397 lines
10 KiB
C
397 lines
10 KiB
C
#ifndef MEM_STATIC
|
|
# define MEM_STATIC
|
|
# define MEM_API static inline
|
|
# include "external/memfun/memfun.h"
|
|
#endif
|
|
|
|
// =============================================================================
|
|
// String conversions
|
|
|
|
internal bool32 char_is_upper(uint8 c)
|
|
{
|
|
return ('A' <= c && c <= 'Z');
|
|
}
|
|
|
|
internal bool32 char_is_lower(uint8 c)
|
|
{
|
|
return ('a' <= c && c <= 'z');
|
|
}
|
|
|
|
internal bool32 char_is_slash(uint8 c)
|
|
{
|
|
return (c == '/' || c == '\\');
|
|
}
|
|
|
|
internal uint8 lower_from_char(uint8 c)
|
|
{
|
|
if (char_is_upper(c))
|
|
{
|
|
c += ('a' - 'A');
|
|
}
|
|
return c;
|
|
}
|
|
|
|
internal uint8 upper_from_char(uint8 c)
|
|
{
|
|
if (char_is_lower(c))
|
|
{
|
|
c += ('A' - 'a');
|
|
}
|
|
return c;
|
|
}
|
|
|
|
internal uint8 correct_slash_from_char(uint8 c)
|
|
{
|
|
if (char_is_slash(c))
|
|
{
|
|
c = '/';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// ========================================================================== //
|
|
// String Length
|
|
|
|
internal uint64 cstring8_length(uint8* cstring)
|
|
{
|
|
uint64 length = 0;
|
|
if (cstring)
|
|
{
|
|
uint8* p = cstring;
|
|
for (; *p != '\0'; ++p)
|
|
;
|
|
length = (uint64)(p - cstring);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
internal uint64 cstring16_length(uint16* cstring)
|
|
{
|
|
uint64 length = 0;
|
|
if (cstring)
|
|
{
|
|
uint16* p = cstring;
|
|
for (; *p != '\0'; ++p)
|
|
;
|
|
length = (uint64)(p - cstring);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
internal uint64 cstring32_length(uint32* cstring)
|
|
{
|
|
uint64 length = 0;
|
|
if (cstring)
|
|
{
|
|
uint32* p = cstring;
|
|
for (; *p != '\0'; ++p)
|
|
;
|
|
length = (uint64)(p - cstring);
|
|
}
|
|
return length;
|
|
}
|
|
|
|
// ========================================================================== //
|
|
// String Constructor
|
|
|
|
internal String8 str8(uint8* str, uint64 size)
|
|
{
|
|
String8 result = { str, size };
|
|
return result;
|
|
}
|
|
|
|
internal String8 str8_zero()
|
|
{
|
|
String8 result = { 0 };
|
|
return result;
|
|
}
|
|
|
|
internal String16 str16(uint16* str, uint64 size)
|
|
{
|
|
String16 result = { str, size };
|
|
return result;
|
|
}
|
|
|
|
internal String16 str16_zero()
|
|
{
|
|
String16 result = { 0 };
|
|
return result;
|
|
}
|
|
|
|
internal String8 str8_cstring(const char* cstring)
|
|
{
|
|
String8 result = { (uint8*)cstring, cstring8_length((uint8*)cstring) };
|
|
return result;
|
|
}
|
|
|
|
internal String16 str16_cstring(uint16* cstring)
|
|
{
|
|
String16 result = { (uint16*)cstring, cstring16_length((uint16*)cstring) };
|
|
return result;
|
|
}
|
|
|
|
internal String32 str32_cstring(uint32* cstring)
|
|
{
|
|
String32 result = { (uint32*)cstring, cstring32_length((uint32*)cstring) };
|
|
return result;
|
|
}
|
|
|
|
// =============================================================================
|
|
// String Matching
|
|
|
|
internal bool32 str8_match(String8 a, String8 b, StringMatchFlags flags)
|
|
{
|
|
bool32 result = 0;
|
|
if (a.size == b.size && flags == 0)
|
|
{
|
|
result = MemIsEqual(a.str, b.str, b.size);
|
|
}
|
|
else if (a.size == b.size && flags == StringMatchFlag_CaseInsensitive)
|
|
{
|
|
result = MemCompareI(a.str, b.str, a.size) == 0;
|
|
}
|
|
else if (a.size == b.size || (flags * StringMatchFlag_RightSideSloppy))
|
|
{
|
|
bool32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive);
|
|
bool32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive);
|
|
uint64 size = Min(a.size, b.size);
|
|
result = 1;
|
|
for
|
|
EachIndex(i, size)
|
|
{
|
|
uint8 at = a.str[i];
|
|
uint8 bt = b.str[i];
|
|
if (case_insensitive)
|
|
{
|
|
at = upper_from_char(at);
|
|
bt = upper_from_char(bt);
|
|
}
|
|
if (slash_insensitive)
|
|
{
|
|
at = correct_slash_from_char(at);
|
|
bt = correct_slash_from_char(bt);
|
|
}
|
|
if (at != bt)
|
|
{
|
|
result = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// =============================================================================
|
|
// String Formatting & Copying
|
|
|
|
internal String8 str8_copy(Arena* arena, String8 in)
|
|
{
|
|
String8 str;
|
|
str.size = in.size;
|
|
str.str = push_array_no_zero(arena, uint8, str.size + 1);
|
|
MemoryCopy(str.str, in.str, in.size);
|
|
str.str[str.size] = '\0';
|
|
return str;
|
|
}
|
|
|
|
internal String8 str8fv(Arena* arena, char* fmt, va_list args)
|
|
{
|
|
va_list args2;
|
|
va_copy(args2, args);
|
|
uint32 needed_bytes = juliet_vsnprintf(0, 0, fmt, args) + 1;
|
|
String8 result = { 0 };
|
|
result.str = push_array_no_zero(arena, uint8, needed_bytes);
|
|
result.size = juliet_vsnprintf((char*)result.str, needed_bytes, fmt, args2);
|
|
result.str[result.size] = '\0';
|
|
va_end(args2);
|
|
return result;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Unicode String Encode/Decode
|
|
|
|
read_only global uint8 utf8_class[32] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 5,
|
|
};
|
|
|
|
internal UnicodeDecode utf8_decode(uint8* str, uint64 max)
|
|
{
|
|
UnicodeDecode result = { 1, max_uint32 };
|
|
uint8 byte = str[0];
|
|
uint8 byte_class = utf8_class[byte >> 3];
|
|
switch (byte_class)
|
|
{
|
|
case 1:
|
|
{
|
|
result.codepoint = byte;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if (1 < max)
|
|
{
|
|
uint8 cont_byte = str[1];
|
|
if (utf8_class[cont_byte >> 3] == 0)
|
|
{
|
|
result.codepoint = (byte & bitmask5) << 6;
|
|
result.codepoint |= (cont_byte & bitmask6);
|
|
result.increment = 2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
if (2 < max)
|
|
{
|
|
uint8 cont_byte[2] = { str[1], str[2] };
|
|
if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0)
|
|
{
|
|
result.codepoint = (byte & bitmask4) << 12;
|
|
result.codepoint |= ((cont_byte[0] & bitmask6) << 6);
|
|
result.codepoint |= (cont_byte[1] & bitmask6);
|
|
result.increment = 3;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
if (3 < max)
|
|
{
|
|
uint8 cont_byte[3] = { str[1], str[2], str[3] };
|
|
if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0 && utf8_class[cont_byte[2] >> 3] == 0)
|
|
{
|
|
result.codepoint = (byte & bitmask3) << 18;
|
|
result.codepoint |= ((cont_byte[0] & bitmask6 << 12));
|
|
result.codepoint |= ((cont_byte[1] & bitmask6) << 6);
|
|
result.codepoint |= (cont_byte[2] & bitmask6);
|
|
result.increment = 4;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal UnicodeDecode utf16_decode(uint16* str, uint64 max)
|
|
{
|
|
UnicodeDecode result = { 1, max_uint32 };
|
|
result.codepoint = str[0];
|
|
result.increment = 1;
|
|
if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000)
|
|
{
|
|
result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000);
|
|
result.increment = 2;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal uint32 utf8_encode(uint8* str, uint32 codepoint)
|
|
{
|
|
uint32 increment = 0;
|
|
if (codepoint <= 0x7f)
|
|
{
|
|
str[0] = (uint8)codepoint;
|
|
increment = 1;
|
|
}
|
|
else if (codepoint <= 0x7FF)
|
|
{
|
|
str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5);
|
|
str[1] = bit8 | (codepoint & bitmask6);
|
|
increment = 2;
|
|
}
|
|
else if (codepoint <= 0xFFFF)
|
|
{
|
|
str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4);
|
|
str[1] = bit8 | ((codepoint >> 6) & bitmask6);
|
|
str[2] = bit8 | (codepoint & bitmask6);
|
|
increment = 3;
|
|
}
|
|
else if (codepoint <= 0x10FFFF)
|
|
{
|
|
str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3);
|
|
str[1] = bit8 | ((codepoint >> 12) & bitmask6);
|
|
str[2] = bit8 | (codepoint >> 6 & bitmask6);
|
|
str[3] = bit8 | (codepoint & bitmask6);
|
|
increment = 4;
|
|
}
|
|
else
|
|
{
|
|
str[0] = '?';
|
|
increment = 1;
|
|
}
|
|
return increment;
|
|
}
|
|
|
|
internal uint32 utf16_encode(uint16* str, uint32 codepoint)
|
|
{
|
|
uint32 increment = 1;
|
|
if (codepoint == max_uint32)
|
|
{
|
|
str[0] = (uint16)'?';
|
|
}
|
|
else if (codepoint < 0x10000)
|
|
{
|
|
str[0] = (uint16)codepoint;
|
|
}
|
|
else
|
|
{
|
|
uint32 v = codepoint - 0x10000;
|
|
str[0] = safe_cast_uint16(0xD800 + (v >> 10));
|
|
str[1] = safe_cast_uint16(0xDC00 + (v & bitmask10));
|
|
increment = 2;
|
|
}
|
|
return increment;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Unicode String Conversions
|
|
|
|
internal String8 str8_from_16(Arena* arena, String16 in)
|
|
{
|
|
String8 result = str8_zero();
|
|
if (in.size)
|
|
{
|
|
uint64 capacity = in.size * 3;
|
|
uint8* str = push_array_no_zero(arena, uint8, capacity + 1);
|
|
uint16* ptr = in.str;
|
|
uint16* opl = ptr + in.size;
|
|
uint64 size = 0;
|
|
UnicodeDecode consume;
|
|
for (; ptr < opl; ptr += consume.increment)
|
|
{
|
|
consume = utf16_decode(ptr, opl - ptr);
|
|
size += utf8_encode(str + size, consume.codepoint);
|
|
}
|
|
str[size] = '\0';
|
|
arena_pop(arena, (capacity - size));
|
|
result = str8(str, size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal String16 str16_from_8(Arena* arena, String8 in)
|
|
{
|
|
String16 result = str16_zero();
|
|
if (in.size)
|
|
{
|
|
uint64 capacity = in.size * 2;
|
|
uint16* str = push_array_no_zero(arena, uint16, capacity + 1);
|
|
uint8* ptr = in.str;
|
|
uint8* opl = ptr + in.size;
|
|
uint64 size = 0;
|
|
UnicodeDecode consume;
|
|
for (; ptr < opl; ptr += consume.increment)
|
|
{
|
|
consume = utf8_decode(ptr, opl - ptr);
|
|
size += utf16_encode(str + size, consume.codepoint);
|
|
}
|
|
str[size] = '\0';
|
|
arena_pop(arena, (capacity - size) * 2);
|
|
result = str16(str, size);
|
|
}
|
|
return result;
|
|
}
|