#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "text/rstring.h"
#include "text/runes.h"
#include "text/utf8/rune.h"
static size_t utf8_length(const char* str) {
size_t length = 0;
while (*str != '\0') {
if (0xf0 == (0xf8 & *str)) {
str += 4;
} else if (0xe0 == (0xf0 & *str)) {
str += 3;
} else if (0xc0 == (0xe0 & *str)) {
str += 2;
} else { str += 1;
}
length++;
}
return length;
}
RuneString rstring_new(void) {
RuneString str = {.runes = NULL, .length = 0, .size = 0, .owning = true};
return str;
}
static RuneString rstring_from_runes(const int32_t* const runes, size_t length, bool owning) {
RuneString str = {
.runes = runes, .length = length, .size = length * sizeof(int32_t), .owning = owning};
return str;
}
RuneString rstring_from_cstring(const char* const utf8str) {
size_t length = utf8_length(utf8str);
int32_t* runes = length > 0 ? runes_from_cstring(utf8str, length) : NULL;
return rstring_from_runes(runes, length, true);
}
char* rstring_to_cstring(RuneString str) {
return runes_to_cstring(str.runes, str.length);
}
void rstring_free(RuneString str) {
if (str.owning && str.runes != NULL) {
free((void*)str.runes);
}
}
int32_t rstring_at(RuneString str, size_t idx) {
if (str.length == 0) {
return 0;
}
if (idx < 0 || idx >= str.length) {
return 0;
};
return str.runes[idx];
}
RuneString rstring_slice(RuneString str, int start, int end) {
if (str.length == 0) {
return rstring_new();
}
start = start < 0 ? (int)str.length + start : start;
start = start < 0 ? 0 : start;
if (start >= (int)str.length) {
return rstring_new();
}
end = end < 0 ? (int)str.length + end : end;
end = end > (int)str.length ? (int)str.length : end;
if (end < 0) {
return rstring_new();
}
if (start >= end) {
return rstring_new();
}
int32_t* at = (int32_t*)str.runes + start;
size_t length = end - start;
RuneString slice = rstring_from_runes(at, length, false);
return slice;
}
RuneString rstring_substring(RuneString str, size_t start, size_t length) {
if (length > str.length - start) {
length = str.length - start;
}
return rstring_slice(str, start, start + length);
}
static bool rstring_contains_after(RuneString str, RuneString other, size_t start) {
if (start + other.length > str.length) {
return false;
}
for (size_t idx = 0; idx < other.length; idx++) {
if (str.runes[start + idx] != other.runes[idx]) {
return false;
}
}
return true;
}
static int rstring_index_char(RuneString str, int32_t rune, size_t start) {
for (size_t idx = start; idx < str.length; idx++) {
if (str.runes[idx] == rune) {
return idx;
}
}
return -1;
}
static int rstring_last_index_char(RuneString str, int32_t rune, size_t end) {
if (end >= str.length) {
return -1;
}
for (int idx = end; idx >= 0; idx--) {
if (str.runes[idx] == rune) {
return idx;
}
}
return -1;
}
static int rstring_index_after(RuneString str, RuneString other, size_t start) {
if (other.length == 0) {
return start;
}
if (str.length == 0 || other.length > str.length) {
return -1;
}
size_t cur_idx = start;
while (cur_idx < str.length) {
int match_idx = rstring_index_char(str, other.runes[0], cur_idx);
if (match_idx == -1) {
return match_idx;
}
if (rstring_contains_after(str, other, match_idx)) {
return match_idx;
}
cur_idx = match_idx + 1;
}
return -1;
}
int rstring_index(RuneString str, RuneString other) {
return rstring_index_after(str, other, 0);
}
int rstring_last_index(RuneString str, RuneString other) {
if (other.length == 0) {
return str.length - 1;
}
if (str.length == 0 || other.length > str.length) {
return -1;
}
int cur_idx = str.length - 1;
while (cur_idx >= 0) {
int match_idx = rstring_last_index_char(str, other.runes[0], cur_idx);
if (match_idx == -1) {
return match_idx;
}
if (rstring_contains_after(str, other, match_idx)) {
return match_idx;
}
cur_idx = match_idx - 1;
}
return -1;
}
bool rstring_like(RuneString pattern, RuneString str) {
size_t pidx = 0, sidx = 0, star_idx = SIZE_MAX, match = 0;
while (sidx < str.length) {
int32_t prune = (pidx < pattern.length) ? pattern.runes[pidx] : 0;
int32_t srune = str.runes[sidx];
if (prune == '%') {
star_idx = ++pidx;
match = ++sidx;
if (pidx == pattern.length) {
return true;
}
} else if (prune == '_' || rune_casefold(prune) == rune_casefold(srune)) {
pidx++;
sidx++;
} else if (star_idx != SIZE_MAX) {
pidx = star_idx;
sidx = match++;
} else {
return false;
}
}
while (pidx < pattern.length && pattern.runes[pidx] == '%') {
pidx++;
}
return pidx == pattern.length;
}
RuneString rstring_translate(RuneString str, RuneString from, RuneString to) {
if (str.length == 0) {
return rstring_new();
}
if (from.length == 0) {
return rstring_from_runes(str.runes, str.length, false);
}
int32_t* runes = calloc(str.length, sizeof(int32_t));
if (runes == NULL) {
return rstring_new();
}
size_t length = 0;
for (size_t idx = 0; idx < str.length; idx++) {
size_t k = 0;
for (; k < from.length && k < to.length; k++) {
if (str.runes[idx] == from.runes[k]) {
runes[length] = to.runes[k];
length++;
break;
}
}
bool ignore = false;
for (; k < from.length; k++) {
if (str.runes[idx] == from.runes[k]) {
ignore = true;
break;
}
}
if (!ignore) {
runes[length] = str.runes[idx];
length++;
}
}
return rstring_from_runes(runes, length, true);
}
RuneString rstring_reverse(RuneString str) {
int32_t* runes = (int32_t*)str.runes;
for (size_t i = 0; i < str.length / 2; i++) {
int32_t r = runes[i];
runes[i] = runes[str.length - 1 - i];
runes[str.length - 1 - i] = r;
}
RuneString res = rstring_from_runes(runes, str.length, false);
return res;
}
RuneString rstring_trim_left(RuneString str, RuneString chars) {
if (str.length == 0) {
return rstring_new();
}
size_t idx = 0;
for (; idx < str.length; idx++) {
if (rstring_index_char(chars, str.runes[idx], 0) == -1) {
break;
}
}
return rstring_slice(str, idx, str.length);
}
RuneString rstring_trim_right(RuneString str, RuneString chars) {
if (str.length == 0) {
return rstring_new();
}
int idx = str.length - 1;
for (; idx >= 0; idx--) {
if (rstring_index_char(chars, str.runes[idx], 0) == -1) {
break;
}
}
return rstring_slice(str, 0, idx + 1);
}
RuneString rstring_trim(RuneString str, RuneString chars) {
if (str.length == 0) {
return rstring_new();
}
size_t left = 0;
for (; left < str.length; left++) {
if (rstring_index_char(chars, str.runes[left], 0) == -1) {
break;
}
}
int right = str.length - 1;
for (; right >= 0; right--) {
if (rstring_index_char(chars, str.runes[right], 0) == -1) {
break;
}
}
return rstring_slice(str, left, right + 1);
}
RuneString rstring_pad_left(RuneString str, size_t length, RuneString fill) {
if (str.length >= length) {
return rstring_substring(str, 0, length);
}
if (fill.length == 0) {
return rstring_from_runes(str.runes, str.length, false);
}
size_t pad_langth = length - str.length;
size_t new_size = (str.length + pad_langth) * sizeof(int32_t);
int32_t* new_runes = malloc(new_size);
if (new_runes == NULL) {
return rstring_new();
}
for (size_t i = 0; i < pad_langth; i++) {
new_runes[i] = fill.runes[i % fill.length];
}
memcpy(&new_runes[pad_langth], str.runes, str.size);
RuneString new_str = rstring_from_runes(new_runes, length, true);
return new_str;
}
RuneString rstring_pad_right(RuneString str, size_t length, RuneString fill) {
if (str.length >= length) {
return rstring_substring(str, 0, length);
}
if (fill.length == 0) {
return rstring_from_runes(str.runes, str.length, false);
}
size_t pad_length = length - str.length;
size_t new_size = (str.length + pad_length) * sizeof(int32_t);
int32_t* new_runes = malloc(new_size);
if (new_runes == NULL) {
return rstring_new();
}
memcpy(new_runes, str.runes, str.size);
for (size_t i = str.length; i < length; i++) {
new_runes[i] = fill.runes[(i - str.length) % fill.length];
}
RuneString new_str = rstring_from_runes(new_runes, length, true);
return new_str;
}
void rstring_print(RuneString str) {
if (str.length == 0) {
printf("'' (len=0)\n");
return;
}
printf("'");
for (size_t i = 0; i < str.length; i++) {
printf("%08x ", str.runes[i]);
}
printf("' (len=%zu)", str.length);
printf("\n");
}