summary refs log tree commit diff
path: root/src/String.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/String.c
initial commit
Diffstat (limited to 'src/String.c')
-rw-r--r--src/String.c1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/src/String.c b/src/String.c
new file mode 100644
index 0000000..537d0de
--- /dev/null
+++ b/src/String.c
@@ -0,0 +1,1027 @@
+#include "String.h"
+#include "Funcs.h"
+#include "Logger.h"
+#include "Platform.h"
+#include "Stream.h"
+#include "Utils.h"
+
+#ifdef __cplusplus
+const cc_string String_Empty = { NULL, 0, 0 };
+#else
+const cc_string String_Empty;
+#endif
+
+int String_CalcLen(const char* raw, int capacity) {
+	int length = 0;
+	while (length < capacity && *raw) { raw++; length++; }
+	return length;
+}
+
+int String_Length(const char* raw) {
+	int length = 0;
+	while (length < UInt16_MaxValue && *raw) { raw++; length++; }
+	return length;
+}
+
+cc_string String_FromRaw(STRING_REF char* buffer, int capacity) {
+	return String_Init(buffer, String_CalcLen(buffer, capacity), capacity);
+}
+
+cc_string String_FromReadonly(STRING_REF const char* buffer) {
+	int len = String_Length(buffer);
+	return String_Init((char*)buffer, len, len);
+}
+
+
+void String_Copy(cc_string* dst, const cc_string* src) {
+	dst->length = 0;
+	String_AppendString(dst, src);
+}
+
+void String_CopyToRaw(char* dst, int capacity, const cc_string* src) {
+	int i, len = min(capacity, src->length);
+	for (i = 0; i < len; i++) { dst[i] = src->buffer[i]; }
+	/* add \0 to mark end of used portion of buffer */
+	if (len < capacity) dst[len] = '\0';
+}
+
+cc_string String_UNSAFE_Substring(STRING_REF const cc_string* str, int offset, int length) {
+	if (offset < 0 || offset > str->length) {
+		Logger_Abort("Offset for substring out of range");
+	}
+	if (length < 0 || length > str->length) {
+		Logger_Abort("Length for substring out of range");
+	}
+	if (offset + length > str->length) {
+		Logger_Abort("Result substring is out of range");
+	}
+	return String_Init(str->buffer + offset, length, length);
+}
+
+cc_string String_UNSAFE_SubstringAt(STRING_REF const cc_string* str, int offset) {
+	cc_string sub;
+	if (offset < 0 || offset > str->length) Logger_Abort("Sub offset out of range");
+
+	sub.buffer   = str->buffer + offset;
+	sub.length   = str->length - offset;
+	sub.capacity = str->length - offset; /* str->length to match String_UNSAFE_Substring */
+	return sub;
+}
+
+int String_UNSAFE_Split(STRING_REF const cc_string* str, char c, cc_string* subs, int maxSubs) {
+	int beg = 0, end, count, i;
+
+	for (i = 0; i < maxSubs && beg <= str->length; i++) 
+	{
+		end = String_IndexOfAt(str, beg, c);
+		if (end == -1 || i == (maxSubs - 1)) end = str->length;
+
+		subs[i] = String_UNSAFE_Substring(str, beg, end - beg);
+		beg = end + 1;
+	}
+
+	count = i;
+	/* If not enough split substrings, make remaining NULL */
+	for (; i < maxSubs; i++) { subs[i] = String_Empty; }
+	return count;
+}
+
+void String_UNSAFE_SplitBy(STRING_REF cc_string* str, char c, cc_string* part) {
+	int idx = String_IndexOf(str, c);
+	if (idx == -1) {
+		*part = *str;
+		*str  = String_Empty;
+	} else {
+		*part = String_UNSAFE_Substring(str, 0, idx); idx++;
+		*str  = String_UNSAFE_SubstringAt(str, idx);
+	}
+}
+
+int String_UNSAFE_Separate(STRING_REF const cc_string* str, char c, cc_string* key, cc_string* value) {
+	int idx = String_IndexOf(str, c);
+	if (idx == -1) {
+		*key   = *str;
+		*value = String_Empty;
+		return false;
+	}
+
+	*key   = String_UNSAFE_Substring(str, 0, idx); idx++;
+	*value = String_UNSAFE_SubstringAt(str, idx);
+
+	/* Trim key [c] value to just key[c]value */
+	String_UNSAFE_TrimEnd(key);
+	String_UNSAFE_TrimStart(value);
+	return key->length > 0 && value->length > 0;
+}
+
+
+int String_Equals(const cc_string* a, const cc_string* b) {
+	return a->length == b->length && Mem_Equal(a->buffer, b->buffer, a->length);
+} 
+
+int String_CaselessEquals(const cc_string* a, const cc_string* b) {
+	int i;
+	char aCur, bCur;
+	if (a->length != b->length) return false;
+
+	for (i = 0; i < a->length; i++) {
+		aCur = a->buffer[i]; Char_MakeLower(aCur);
+		bCur = b->buffer[i]; Char_MakeLower(bCur);
+		if (aCur != bCur) return false;
+	}
+	return true;
+}
+
+int String_CaselessEqualsConst(const cc_string* a, const char* b) {
+	int i;
+	char aCur, bCur;
+
+	for (i = 0; i < a->length; i++) {
+		aCur = a->buffer[i]; Char_MakeLower(aCur);
+		bCur = b[i];         Char_MakeLower(bCur);
+		if (aCur != bCur || bCur == '\0') return false;
+	}
+	/* ensure at end of string */
+	return b[a->length] == '\0';
+}
+
+
+void String_Append(cc_string* str, char c) {
+	/* MSVC in debug mode will initialise all variables on the stack with 0xCC by default */
+	/* So if a string is being passed with CC in all its fields, then it's probably invalid */
+#if _MSC_VER && _DEBUG
+	if (str->length == 0xCCCC && str->capacity == 0xCCCC) 
+		Logger_Abort("String must be initialised before calling String_Append");
+#endif
+
+	if (str->length == str->capacity) return;
+	str->buffer[str->length++] = c;
+}
+
+void String_AppendBool(cc_string* str, cc_bool value) {
+	String_AppendConst(str, value ? "True" : "False");
+}
+
+int String_MakeUInt32(cc_uint32 num, char* digits) {
+	int len = 0;
+	do {
+		digits[len] = '0' + (num % 10); num /= 10; len++;
+	} while (num > 0);
+	return len;
+}
+
+void String_AppendInt(cc_string* str, int num) {
+	if (num < 0) {
+		num = -num;
+		String_Append(str, '-');
+	}
+	String_AppendUInt32(str, (cc_uint32)num);
+}
+
+void String_AppendUInt32(cc_string* str, cc_uint32 num) {
+	char digits[STRING_INT_CHARS];
+	int i, count = String_MakeUInt32(num, digits);
+
+	for (i = count - 1; i >= 0; i--) {
+		String_Append(str, digits[i]);
+	}
+}
+
+void String_AppendPaddedInt(cc_string* str, int num, int minDigits) {
+	char digits[STRING_INT_CHARS];
+	int i, count;
+	for (i = 0; i < minDigits; i++) { digits[i] = '0'; }
+
+	count = String_MakeUInt32(num, digits);
+	if (count < minDigits) count = minDigits;
+
+	for (i = count - 1; i >= 0; i--) {
+		String_Append(str, digits[i]);
+	}
+}
+
+void String_AppendFloat(cc_string* str, float num, int fracDigits) {
+	int i, whole, digit;
+	double frac;
+
+	if (num < 0.0f) {
+		String_Append(str, '-'); /* don't need to check success */
+		num = -num;
+	}
+
+	whole = (int)num;
+	String_AppendUInt32(str, whole);
+
+	frac = (double)num - (double)whole;
+	if (frac == 0.0) return;
+	String_Append(str, '.'); /* don't need to check success */
+
+	for (i = 0; i < fracDigits; i++) {
+		frac *= 10;
+		digit = (int)frac % 10;
+		String_Append(str, '0' + digit);
+	}
+}
+
+void String_AppendHex(cc_string* str, cc_uint8 value) {
+	/* 48 = index of 0, 55 = index of (A - 10) */
+	cc_uint8 hi = (value >> 4) & 0xF;
+	char c_hi  = hi < 10 ? (hi + 48) : (hi + 55);
+	cc_uint8 lo = value & 0xF;
+	char c_lo  = lo < 10 ? (lo + 48) : (lo + 55);
+
+	String_Append(str, c_hi);
+	String_Append(str, c_lo);
+}
+
+CC_NOINLINE static void String_Hex32(cc_string* str, cc_uint32 value) {
+	int shift;
+
+	for (shift = 24; shift >= 0; shift -= 8) {
+		cc_uint8 part = (cc_uint8)(value >> shift);
+		String_AppendHex(str, part);
+	}
+}
+
+CC_NOINLINE static void String_Hex64(cc_string* str, cc_uint64 value) {
+	int shift;
+
+	for (shift = 56; shift >= 0; shift -= 8) {
+		cc_uint8 part = (cc_uint8)(value >> shift);
+		String_AppendHex(str, part);
+	}
+}
+
+void String_AppendConst(cc_string* str, const char* src) {
+	for (; *src; src++) {
+		String_Append(str, *src);
+	}
+}
+
+void String_AppendAll(cc_string* str, const void* data, int len) {
+	const char* src = (const char*)data;
+	int i;
+	for (i = 0; i < len; i++) String_Append(str, src[i]);
+}
+
+void String_AppendString(cc_string* str, const cc_string* src) {
+	int i;
+	for (i = 0; i < src->length; i++) {
+		String_Append(str, src->buffer[i]);
+	}
+}
+
+void String_AppendColorless(cc_string* str, const cc_string* src) {
+	char c;
+	int i;
+
+	for (i = 0; i < src->length; i++) {
+		c = src->buffer[i];
+		if (c == '&') { i++; continue; } /* Skip over the following color code */
+		String_Append(str, c);
+	}
+}
+
+
+int String_IndexOfAt(const cc_string* str, int offset, char c) {
+	int i;
+	for (i = offset; i < str->length; i++) {
+		if (str->buffer[i] == c) return i;
+	}
+	return -1;
+}
+
+int String_LastIndexOfAt(const cc_string* str, int offset, char c) {
+	int i;
+	for (i = (str->length - 1) - offset; i >= 0; i--) {
+		if (str->buffer[i] == c) return i;
+	}
+	return -1;
+}
+
+void String_InsertAt(cc_string* str, int offset, char c) {
+	int i;
+
+	if (offset < 0 || offset > str->length) {
+		Logger_Abort("Offset for InsertAt out of range");
+	}
+	if (str->length == str->capacity) {
+		Logger_Abort("Cannot insert character into full string");
+	}
+	
+	for (i = str->length; i > offset; i--) {
+		str->buffer[i] = str->buffer[i - 1];
+	}
+	str->buffer[offset] = c;
+	str->length++;
+}
+
+void String_DeleteAt(cc_string* str, int offset) {
+	int i;
+
+	if (offset < 0 || offset >= str->length) {
+		Logger_Abort("Offset for DeleteAt out of range");
+	}
+	
+	for (i = offset; i < str->length - 1; i++) {
+		str->buffer[i] = str->buffer[i + 1];
+	}
+	str->buffer[str->length - 1] = '\0';
+	str->length--;
+}
+
+void String_UNSAFE_TrimStart(cc_string* str) {
+	int i;
+	for (i = 0; i < str->length; i++) {
+		if (str->buffer[i] != ' ') break;
+			
+		str->buffer++;
+		str->length--; i--;
+	}
+}
+
+void String_UNSAFE_TrimEnd(cc_string* str) {
+	int i;
+	for (i = str->length - 1; i >= 0; i--) {
+		if (str->buffer[i] != ' ') break;
+		str->length--;
+	}
+}
+
+int String_IndexOfConst(const cc_string* str, const char* sub) {
+	int i, j;
+
+	for (i = 0; i < str->length; i++) {
+		for (j = 0; sub[j] && (i + j) < str->length; j++) {
+
+			if (str->buffer[i + j] != sub[j]) break;
+		}
+		if (sub[j] == '\0') return i;
+	}
+	return -1;
+}
+
+int String_CaselessContains(const cc_string* str, const cc_string* sub) {
+	char strCur, subCur;
+	int i, j;
+
+	for (i = 0; i < str->length; i++) {
+		for (j = 0; j < sub->length && (i + j) < str->length; j++) {
+
+			strCur = str->buffer[i + j]; Char_MakeLower(strCur);
+			subCur = sub->buffer[j];     Char_MakeLower(subCur);
+			if (strCur != subCur) break;
+		}
+		if (j == sub->length) return true;
+	}
+	return false;
+}
+
+int String_CaselessStarts(const cc_string* str, const cc_string* sub) {
+	char strCur, subCur;
+	int i;
+	if (str->length < sub->length) return false;
+
+	for (i = 0; i < sub->length; i++) {
+		strCur = str->buffer[i]; Char_MakeLower(strCur);
+		subCur = sub->buffer[i]; Char_MakeLower(subCur);
+		if (strCur != subCur) return false;
+	}
+	return true;
+}
+
+int String_CaselessEnds(const cc_string* str, const cc_string* sub) {
+	char strCur, subCur;
+	int i, j = str->length - sub->length;	
+	if (j < 0) return false; /* sub longer than str */
+
+	for (i = 0; i < sub->length; i++) {
+		strCur = str->buffer[j + i]; Char_MakeLower(strCur);
+		subCur = sub->buffer[i];     Char_MakeLower(subCur);
+		if (strCur != subCur) return false;
+	}
+	return true;
+}
+
+int String_Compare(const cc_string* a, const cc_string* b) {
+	char aCur, bCur;
+	int i, minLen = min(a->length, b->length);
+
+	for (i = 0; i < minLen; i++) {
+		aCur = a->buffer[i]; Char_MakeLower(aCur);
+		bCur = b->buffer[i]; Char_MakeLower(bCur);
+
+		if (aCur == bCur) continue;
+		return (cc_uint8)aCur - (cc_uint8)bCur;
+	}
+
+	/* all chars are equal here - same string, or a substring */
+	return a->length - b->length;
+}
+
+void String_Format1(cc_string* str, const char* format, const void* a1) {
+	String_Format4(str, format, a1, NULL, NULL, NULL);
+}
+void String_Format2(cc_string* str, const char* format, const void* a1, const void* a2) {
+	String_Format4(str, format, a1, a2, NULL, NULL);
+}
+void String_Format3(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3) {
+	String_Format4(str, format, a1, a2, a3, NULL);
+}
+void String_Format4(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3, const void* a4) {
+	const void* arg;
+	int i, j = 0, digits;
+
+	const void* args[4];
+	args[0] = a1; args[1] = a2; args[2] = a3; args[3] = a4;
+
+	for (i = 0; format[i]; i++) {
+		if (format[i] != '%') { String_Append(str, format[i]); continue; }
+		arg = args[j++];
+
+		switch (format[++i]) {
+		case 'b': 
+			String_AppendInt(str, *((cc_uint8*)arg)); 
+			break;
+		case 'i': 
+			String_AppendInt(str, *((int*)arg)); 
+			break;
+		case 'f': 
+			digits = format[++i] - '0';
+			String_AppendFloat(str, *((float*)arg), digits); 
+			break;
+		case 'p':
+			digits = format[++i] - '0';
+			String_AppendPaddedInt(str, *((int*)arg), digits); 
+			break;
+		case 't': 
+			String_AppendBool(str, *((cc_bool*)arg)); 
+			break;
+		case 'c': 
+			String_AppendConst(str, (char*)arg);  
+			break;
+		case 's': 
+			String_AppendString(str, (cc_string*)arg);  
+			break;
+		case 'r':
+			String_Append(str, *((char*)arg)); 
+			break;
+		case 'x':
+			if (sizeof(cc_uintptr) == 4) {
+				String_Hex32(str, *((cc_uint32*)arg));
+			} else {
+				String_Hex64(str, *((cc_uint64*)arg));
+			}
+			break;
+		case 'h':
+			String_Hex32(str, *((cc_uint32*)arg)); 
+			break;
+		case 'e':
+			digits = *((int*)arg);
+			if (digits >= -0xFFFF && digits <= 0xFFFF) {
+				String_AppendInt(str, digits);
+			} else {
+				String_Hex32(str, (cc_uint32)digits); 
+			}
+			break;
+		case '%':
+			String_Append(str, '%'); 
+			break;
+		default: 
+			Logger_Abort("Invalid type for string format");
+		}
+	}
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------Character set conversions------------------------------------------------*
+*#########################################################################################################################*/
+static const cc_unichar controlChars[32] = {
+	0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+	0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
+	0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
+	0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC
+};
+
+static const cc_unichar extendedChars[129] = { 0x2302,
+	0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
+	0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+	0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
+	0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+	0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
+	0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+	0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+	0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+	0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+	0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+	0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+	0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+	0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+cc_unichar Convert_CP437ToUnicode(char c) {
+	cc_uint8 raw = (cc_uint8)c;
+	if (raw < 0x20) return controlChars[raw];
+	if (raw < 0x7F) return raw;
+	return extendedChars[raw - 0x7F];
+}
+
+char Convert_CodepointToCP437(cc_codepoint cp) {
+	char c; Convert_TryCodepointToCP437(cp, &c); return c;
+}
+
+static cc_codepoint ReduceEmoji(cc_codepoint cp) {
+	if (cp == 0x1F31E) return 0x263C;
+	if (cp == 0x1F3B5) return 0x266B;
+	if (cp == 0x1F642) return 0x263A;
+
+	if (cp == 0x1F600 || cp == 0x1F601 || cp == 0x1F603) return 0x263A;
+	if (cp == 0x1F604 || cp == 0x1F606 || cp == 0x1F60A) return 0x263A;
+	return cp;
+}
+
+cc_bool Convert_TryCodepointToCP437(cc_codepoint cp, char* c) {
+	int i;
+	if (cp >= 0x20 && cp < 0x7F) { *c = (char)cp; return true; }
+	if (cp >= 0x1F000) cp = ReduceEmoji(cp);
+
+	for (i = 0; i < Array_Elems(controlChars); i++) {
+		if (controlChars[i] == cp) { *c = i; return true; }
+	}
+	for (i = 0; i < Array_Elems(extendedChars); i++) {
+		if (extendedChars[i] == cp) { *c = i + 0x7F; return true; }
+	}
+
+	*c = '?'; return false;
+}
+
+int Convert_Utf8ToCodepoint(cc_codepoint* cp, const cc_uint8* data, cc_uint32 len) {
+	*cp = '\0';
+	if (!len) return 0;
+
+	if (data[0] <= 0x7F) {
+		*cp = data[0];
+		return 1;
+	} else if ((data[0] & 0xE0) == 0xC0) {
+		if (len < 2) return 0;
+
+		*cp = ((data[0] & 0x1F) << 6)  | ((data[1] & 0x3F));
+		return 2;
+	} else if ((data[0] & 0xF0) == 0xE0) {
+		if (len < 3) return 0;
+
+		*cp = ((data[0] & 0x0F) << 12) | ((data[1] & 0x3F) << 6) 
+			| ((data[2] & 0x3F));
+		return 3;
+	} else {
+		if (len < 4) return 0;
+
+		*cp = ((data[0] & 0x07) << 18) | ((data[1] & 0x3F) << 12) 
+			| ((data[2] & 0x3F) << 6)  |  (data[3] & 0x3F);
+		return 4;
+	}
+}
+
+/* Encodes a unicode character in UTF8, returning number of bytes written */
+static int Convert_UnicodeToUtf8(cc_unichar uc, cc_uint8* data) {
+	if (uc <= 0x7F) {
+		data[0] = (cc_uint8)uc;
+		return 1;
+	} else if (uc <= 0x7FF) {
+		data[0] = 0xC0 | ((uc >> 6) & 0x1F);
+		data[1] = 0x80 | ((uc)      & 0x3F);
+		return 2;
+	} else {
+		data[0] = 0xE0 | ((uc >> 12) & 0x0F);
+		data[1] = 0x80 | ((uc >>  6) & 0x3F);
+		data[2] = 0x80 | ((uc)       & 0x3F);
+		return 3;
+	}
+}
+
+int Convert_CP437ToUtf8(char c, cc_uint8* data) {
+	/* Common ASCII case */
+	if (c >= 0x20 && c < 0x7F) {
+		data[0] = (cc_uint8)c;
+		return 1;
+	}
+	return Convert_UnicodeToUtf8(Convert_CP437ToUnicode(c), data);
+}
+
+void String_AppendUtf16(cc_string* value, const void* data, int numBytes) {
+	const cc_unichar* chars = (const cc_unichar*)data;
+	int i; char c;
+	
+	for (i = 0; i < (numBytes >> 1); i++) {
+		/* TODO: UTF16 to codepoint conversion */
+		if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c);
+	}
+}
+
+void String_AppendUtf8(cc_string* value, const void* data, int numBytes) {
+	const cc_uint8* chars = (const cc_uint8*)data;
+	int len; cc_codepoint cp; char c;
+
+	for (; numBytes > 0; numBytes -= len) {
+		len = Convert_Utf8ToCodepoint(&cp, chars, numBytes);
+		if (!len) return;
+
+		if (Convert_TryCodepointToCP437(cp, &c)) String_Append(value, c);
+		chars += len;
+	}
+}
+
+void String_DecodeCP1252(cc_string* value, const void* data, int numBytes) {
+	const cc_uint8* chars = (const cc_uint8*)data;
+	int i; char c;
+
+	for (i = 0; i < numBytes; i++) {
+		if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c);
+	}
+}
+
+int String_EncodeUtf8(void* data, const cc_string* src) {
+	cc_uint8* dst = (cc_uint8*)data;
+	cc_uint8* cur;
+	int i, len = 0;
+	if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand");
+
+	for (i = 0; i < src->length; i++) {
+		cur = dst + len;
+		len += Convert_CP437ToUtf8(src->buffer[i], cur);
+	}
+	dst[len] = '\0';
+	return len;
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------Numerical conversions--------------------------------------------------*
+*#########################################################################################################################*/
+cc_bool Convert_ParseUInt8(const cc_string* str, cc_uint8* value) {
+	int tmp; 
+	*value = 0;
+	if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt8_MaxValue) return false;
+	*value = (cc_uint8)tmp; return true;
+}
+
+cc_bool Convert_ParseUInt16(const cc_string* str, cc_uint16* value) {
+	int tmp; 
+	*value = 0;
+	if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt16_MaxValue) return false;
+	*value = (cc_uint16)tmp; return true;
+}
+
+static int Convert_CompareDigits(const char* digits, const char* magnitude) {
+	int i;
+	for (i = 0; ; i++) {
+		if (magnitude[i] == '\0')     return  0;
+		if (digits[i] > magnitude[i]) return  1;
+		if (digits[i] < magnitude[i]) return -1;
+	}
+	return 0;
+}
+
+static cc_bool Convert_TryParseDigits(const cc_string* str, cc_bool* negative, char* digits, int maxDigits) {
+	char* start = digits;
+	int offset = 0, i;
+
+	*negative = false;
+	if (!str->length) return false;
+	digits += (maxDigits - 1);
+
+	/* Handle number signs */
+	if (str->buffer[0] == '-') { *negative = true; offset = 1; }
+	if (str->buffer[0] == '+') { offset = 1; }
+
+	/* add digits, starting at last digit */
+	for (i = str->length - 1; i >= offset; i--) 
+	{
+		char c = str->buffer[i];
+		if (c < '0' || c > '9' || digits < start) return false;
+		*digits-- = c;
+	}
+
+	for (; digits >= start; ) { *digits-- = '0'; }
+	return true;
+}
+
+#define INT32_DIGITS 10
+cc_bool Convert_ParseInt(const cc_string* str, int* value) {
+	cc_bool negative;
+	char digits[INT32_DIGITS];
+	int i, compare, sum = 0;
+
+	*value = 0;	
+	if (!Convert_TryParseDigits(str, &negative, digits, INT32_DIGITS)) return false;
+	
+	if (negative) {
+		compare = Convert_CompareDigits(digits, "2147483648");
+		/* Special case, since |largest min value| is > |largest max value| */
+		if (compare == 0) { *value = Int32_MinValue; return true; }
+	} else {
+		compare = Convert_CompareDigits(digits, "2147483647");
+	}
+	if (compare > 0) return false;
+
+	for (i = 0; i < INT32_DIGITS; i++) {
+		sum *= 10; sum += digits[i] - '0';
+	}
+
+	if (negative) sum = -sum;
+	*value = sum;
+	return true;
+}
+
+#define UINT64_DIGITS 20
+cc_bool Convert_ParseUInt64(const cc_string* str, cc_uint64* value) {
+	cc_bool negative;
+	char digits[UINT64_DIGITS];
+	int i, compare;
+	cc_uint64 sum = 0;
+
+	*value = 0;
+	if (!Convert_TryParseDigits(str, &negative, digits, UINT64_DIGITS)) return false;
+
+	compare = Convert_CompareDigits(digits, "18446744073709551615");
+	if (negative || compare > 0) return false;
+
+	for (i = 0; i < UINT64_DIGITS; i++) {
+		sum *= 10; sum += digits[i] - '0';
+	}
+
+	*value = sum;
+	return true;
+}
+
+cc_bool Convert_ParseFloat(const cc_string* str, float* value) {
+	int i = 0;
+	cc_bool negate = false;
+	double sum, whole, fract, divide = 10.0;
+	*value = 0.0f;
+
+	/* Handle number signs */
+	if (!str->length) return false;
+	if (str->buffer[0] == '-') { negate = true; i++; }
+	if (str->buffer[0] == '+') { i++; }
+
+	for (whole = 0.0; i < str->length; i++) {
+		char c = str->buffer[i];
+		if (c == '.' || c == ',') { i++; break; }
+
+		if (c < '0' || c > '9') return false;
+		whole *= 10; whole += (c - '0');
+	}
+
+	for (fract = 0.0; i < str->length; i++) {
+		char c = str->buffer[i];
+		if (c < '0' || c > '9') return false;
+
+		fract += (c - '0') / divide; divide *= 10;
+	}
+
+	sum = whole + fract;
+	if (negate) sum = -sum;
+	*value = (float)sum;
+	return true;
+}
+
+cc_bool Convert_ParseBool(const cc_string* str, cc_bool* value) {
+	if (String_CaselessEqualsConst(str, "True")) {
+		*value = true; return true;
+	}
+	if (String_CaselessEqualsConst(str, "False")) {
+		*value = false; return true;
+	}
+	*value = false; return false;
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------StringsBuffer------------------------------------------------------*
+*#########################################################################################################################*/
+#define STRINGSBUFFER_BUFFER_EXPAND_SIZE 8192
+
+#define StringsBuffer_GetOffset(raw)  ((raw) >> buffer->_lenShift)
+#define StringsBuffer_GetLength(raw)  ((raw)  & buffer->_lenMask)
+#define StringsBuffer_PackOffset(off) ((off) << buffer->_lenShift)
+
+void StringsBuffer_Init(struct StringsBuffer* buffer) {
+	buffer->count       = 0;
+	buffer->totalLength = 0;
+	buffer->textBuffer     = buffer->_defaultBuffer;
+	buffer->flagsBuffer    = buffer->_defaultFlags;
+	buffer->_textCapacity  = STRINGSBUFFER_BUFFER_DEF_SIZE;
+	buffer->_flagsCapacity = STRINGSBUFFER_FLAGS_DEF_ELEMS;
+
+	if (buffer->_lenShift) return;
+	StringsBuffer_SetLengthBits(buffer, STRINGSBUFFER_DEF_LEN_SHIFT);
+}
+
+void StringsBuffer_SetLengthBits(struct StringsBuffer* buffer, int bits) {
+	buffer->_lenShift = bits;
+	buffer->_lenMask  = (1 << bits) - 1;
+}
+
+void StringsBuffer_Clear(struct StringsBuffer* buffer) {
+	/* Never initialised to begin with */
+	if (!buffer->_flagsCapacity) return;
+
+	if (buffer->textBuffer != buffer->_defaultBuffer) {
+		Mem_Free(buffer->textBuffer);
+	}
+	if (buffer->flagsBuffer != buffer->_defaultFlags) {
+		Mem_Free(buffer->flagsBuffer);
+	}
+	StringsBuffer_Init(buffer);
+}
+
+cc_string StringsBuffer_UNSAFE_Get(struct StringsBuffer* buffer, int i) {
+	cc_uint32 flags, offset, len;
+	if (i < 0 || i >= buffer->count) Logger_Abort("Tried to get String past StringsBuffer end");
+
+	flags  = buffer->flagsBuffer[i];
+	offset = StringsBuffer_GetOffset(flags);
+	len    = StringsBuffer_GetLength(flags);
+	return String_Init(&buffer->textBuffer[offset], len, len);
+}
+
+void StringsBuffer_UNSAFE_GetRaw(struct StringsBuffer* buffer, int i, cc_string* dst) {
+	cc_uint32 flags = buffer->flagsBuffer[i];
+	dst->buffer     = buffer->textBuffer + StringsBuffer_GetOffset(flags);
+	dst->length     = StringsBuffer_GetLength(flags);
+	dst->capacity   = 0;
+}
+
+void StringsBuffer_Add(struct StringsBuffer* buffer, const cc_string* str) {
+	int textOffset;
+	/* StringsBuffer hasn't been initialised yet, do it here */
+	if (!buffer->_flagsCapacity) StringsBuffer_Init(buffer);
+
+	if (buffer->count == buffer->_flagsCapacity) {
+		Utils_Resize((void**)&buffer->flagsBuffer, &buffer->_flagsCapacity,
+					4, STRINGSBUFFER_FLAGS_DEF_ELEMS, 512);
+	}
+
+	if (str->length > buffer->_lenMask) {
+		Logger_Abort("String too big to insert into StringsBuffer");
+	}
+
+	textOffset = buffer->totalLength;
+	if (textOffset + str->length >= buffer->_textCapacity) {
+		Utils_Resize((void**)&buffer->textBuffer, &buffer->_textCapacity,
+					1, STRINGSBUFFER_BUFFER_DEF_SIZE, 8192);
+	}
+
+	Mem_Copy(&buffer->textBuffer[textOffset], str->buffer, str->length);
+	buffer->flagsBuffer[buffer->count] = str->length | StringsBuffer_PackOffset(textOffset);
+
+	buffer->count++;
+	buffer->totalLength += str->length;
+}
+
+void StringsBuffer_Remove(struct StringsBuffer* buffer, int index) {
+	cc_uint32 flags, offset, len;
+	cc_uint32 i, offsetAdj;
+	if (index < 0 || index >= buffer->count) Logger_Abort("Tried to remove String past StringsBuffer end");
+
+	flags  = buffer->flagsBuffer[index];
+	offset = StringsBuffer_GetOffset(flags);
+	len    = StringsBuffer_GetLength(flags);
+
+	/* Imagine buffer is this: AAXXYYZZ, and want to delete XX */
+	/* We iterate from first char of Y to last char of Z, */
+	/* shifting each character two to the left. */
+	for (i = offset + len; i < buffer->totalLength; i++) {
+		buffer->textBuffer[i - len] = buffer->textBuffer[i]; 
+	}
+
+	/* Adjust text offset of elements after this element */
+	/* Elements may not be in order so must account for that */
+	offsetAdj = StringsBuffer_PackOffset(len);
+	for (i = index; i < buffer->count - 1; i++) {
+		buffer->flagsBuffer[i] = buffer->flagsBuffer[i + 1];
+		if (buffer->flagsBuffer[i] >= flags) {
+			buffer->flagsBuffer[i] -= offsetAdj;
+		}
+	}
+	
+	buffer->count--;
+	buffer->totalLength -= len;
+}
+
+static struct StringsBuffer* sort_buffer;
+static void StringsBuffer_QuickSort(int left, int right) {
+	struct StringsBuffer* buffer = sort_buffer;
+	cc_uint32* keys = buffer->flagsBuffer; cc_uint32 key;
+
+	while (left < right) {
+		int i = left, j = right;
+		cc_string pivot = StringsBuffer_UNSAFE_Get(buffer, (i + j) >> 1);
+		cc_string strI, strJ;
+
+		/* partition the list */
+		while (i <= j) {
+			while ((strI = StringsBuffer_UNSAFE_Get(buffer, i), String_Compare(&pivot, &strI)) > 0) i++;
+			while ((strJ = StringsBuffer_UNSAFE_Get(buffer, j), String_Compare(&pivot, &strJ)) < 0) j--;
+			QuickSort_Swap_Maybe();
+		}
+		/* recurse into the smaller subset */
+		QuickSort_Recurse(StringsBuffer_QuickSort)
+	}
+}
+
+void StringsBuffer_Sort(struct StringsBuffer* buffer) {
+	sort_buffer = buffer;
+	StringsBuffer_QuickSort(0, buffer->count - 1);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Word wrapper-------------------------------------------------------*
+*#########################################################################################################################*/
+static cc_bool WordWrap_IsWrapper(char c) {
+	return c == '\0' || c == ' ' || c == '-' || c == '>' || c == '<' || c == '/' || c == '\\';
+}
+
+void WordWrap_Do(STRING_REF cc_string* text, cc_string* lines, int numLines, int lineLen) {
+	int i, lineStart, lineEnd;
+	for (i = 0; i < numLines; i++) { lines[i] = String_Empty; }
+
+	for (i = 0, lineStart = 0; i < numLines; i++) {
+		int nextLineStart = lineStart + lineLen;
+		/* No more text to wrap */
+		if (nextLineStart >= text->length) {
+			lines[i] = String_UNSAFE_SubstringAt(text, lineStart); return;
+		}
+
+		/* Find beginning of last word on current line */
+		for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) {
+			if (WordWrap_IsWrapper(text->buffer[lineEnd])) break;
+		}
+		lineEnd++; /* move after wrapper char (i.e. beginning of last word) */
+
+		if (lineEnd <= lineStart || lineEnd >= nextLineStart) {
+			/* Three special cases handled by this: */
+			/* - Entire line is filled with a single word */
+			/* - Last character(s) on current line are wrapper characters */
+			/* - First character on next line is a wrapper character (last word ends at current line end) */
+			lines[i] = String_UNSAFE_Substring(text, lineStart, lineLen);
+			lineStart += lineLen;
+		} else {
+			/* Last word in current line does not end in current line (extends onto next line) */
+			/* Trim current line to end at beginning of last word */
+			/* Set next line to start at beginning of last word */
+			lines[i] = String_UNSAFE_Substring(text, lineStart, lineEnd - lineStart);
+			lineStart = lineEnd;
+		}
+	}
+}
+
+void WordWrap_GetCoords(int index, const cc_string* lines, int numLines, int* coordX, int* coordY) {
+	int y, offset = 0, lineLen;
+	if (index == -1) index = Int32_MaxValue;
+	*coordX = -1; *coordY = 0;
+
+	for (y = 0; y < numLines; y++) {
+		lineLen = lines[y].length;
+		if (!lineLen) break;
+
+		*coordY = y;
+		if (index < offset + lineLen) {
+			*coordX = index - offset; break;
+		}
+		offset += lineLen;
+	}
+	if (*coordX == -1) *coordX = lines[*coordY].length;
+}
+
+int WordWrap_GetBackLength(const cc_string* text, int index) {
+	int start = index;
+	if (index <= 0) return 0;
+	if (index >= text->length) Logger_Abort("WordWrap_GetBackLength - index past end of string");
+	
+	/* Go backward to the end of the previous word */
+	while (index > 0 && text->buffer[index] == ' ') index--;
+	/* Go backward to the start of the current word */
+	while (index > 0 && text->buffer[index] != ' ') index--;
+
+	return start - index;
+}
+
+int WordWrap_GetForwardLength(const cc_string* text, int index) {
+	int start = index, length = text->length;
+	if (index == -1) return 0;
+	if (index >= text->length) Logger_Abort("WordWrap_GetForwardLength - index past end of string");
+
+	/* Go forward to the end of the word 'index' is currently in */
+	while (index < length && text->buffer[index] != ' ') index++;
+	/* Go forward to the start of the next word after 'index' */
+	while (index < length && text->buffer[index] == ' ') index++;
+
+	return index - start;
+}
\ No newline at end of file