power_play/src/base/base_string.c

959 lines
22 KiB
C

////////////////////////////////////////////////////////////
//~ Conversion helpers
//- Char conversion
String StringFromChar(Arena *arena, char c)
{
u8 *dst = PushStructNoZero(arena, u8);
*dst = c;
return (String)
{
.len = 1,
.text = dst
};
}
//- Unsigned int conversion
String StringFromU64(Arena *arena, u64 n, u64 base, u64 zfill)
{
/* Base too large */
Assert(base <= (countof(IntChars) - 1));
TempArena scratch = BeginScratch(arena);
/* Build backwards text starting from least significant digit */
u64 len = 0;
u8 *backwards_text = PushDry(scratch.arena, u8);
do
{
StringFromChar(scratch.arena, IntChars[n % base]);
++len;
n /= base;
} while (n > 0);
while (len < zfill)
{
StringFromChar(scratch.arena, '0');
++len;
}
/* Reverse text into final string */
u8 *final_text = PushStructsNoZero(arena, u8, len);
for (u64 i = 0; i < len; ++i)
{
final_text[i] = backwards_text[len - i - 1];
}
EndScratch(scratch);
return (String)
{
.len = len,
.text = final_text
};
}
//- Signed int conversion
String StringFromI64(Arena *arena, i64 n, u64 base, u64 zfill)
{
u8 *final_text = PushDry(arena, u8);
u8 len = 0;
if (n < 0)
{
/* Push sign */
StringFromChar(arena, '-');
len = 1;
n = -n;
}
/* Push unsigned number */
String uint_str = StringFromU64(arena, n, base, zfill);
return (String)
{
.len = len + uint_str.len,
.text = final_text
};
}
//- Pointer conversion
String StringFromPtr(Arena *arena, void *ptr)
{
String prepend = PushString(arena, Lit("0x"));
String uint_str = StringFromU64(arena, (u64)ptr, 16, sizeof(ptr));
return (String)
{
.len = prepend.len + uint_str.len,
.text = prepend.text
};
}
//- Floating point conversion
String StringFromF64(Arena *arena, f64 f, u32 precision)
{
TempArena scratch = BeginScratch(arena);
String result = ZI;
result.text = PushDry(arena, u8);
if (IsF32Nan(f))
{
result.len += PushString(arena, Lit("NaN")).len;
}
else if (f == F64Infinity)
{
result.len += PushString(arena, Lit("inf")).len;
}
else if (f == -F64Infinity)
{
result.len += PushString(arena, Lit("-inf")).len;
}
else
{
if (f < 0)
{
StringFromChar(arena, '-');
f = -f;
++result.len;
}
/* Add one half of next precision level to round up */
f += 0.5 / (f64)PowU64(10, (u8)precision);
f64 part_whole = TruncF64(f);
f64 part_decimal = f - part_whole;
/* Print whole part */
{
/* Build backwards text starting from least significant digit */
u8 *backwards_text = PushDry(scratch.arena, u8);
u64 backwards_text_len = 0;
do
{
u64 digit = (u64)RoundF64ToI64(ModF64(part_whole, 10.0));
StringFromChar(scratch.arena, IntChars[digit % 10]);
++backwards_text_len;
part_whole = TruncF64(part_whole / 10.0);
} while (part_whole > 0);
/* Reverse text into final string */
PushStructsNoZero(arena, u8, backwards_text_len);
for (u64 i = backwards_text_len; i-- > 0;)
{
result.text[result.len++] = backwards_text[i];
}
}
/* Print decimal part */
if (precision > 0)
{
StringFromChar(arena, '.');
for (u64 i = 0; i < precision; ++i)
{
part_decimal *= 10.0;
u64 digit = (u64)part_decimal;
part_decimal -= digit;
StringFromChar(arena, IntChars[digit % 10]);
}
result.len += (u64)precision + 1;
}
}
EndScratch(scratch);
return result;
}
//- Handle conversion
String StringFromhandle(Arena *arena, u64 v0, u64 v1)
{
String result = ZI;
result.text = PushDry(arena, u8);
result.len += PushString(arena, Lit("h")).len;
result.len += StringFromU64(arena, v0, 16, 0).len;
result.len += PushString(arena, Lit("x")).len;
result.len += StringFromU64(arena, v1, 16, 0).len;
return result;
}
//- Uid conversion
String StringFromUid(Arena *arena, Uid uid)
{
String result = ZI;
result.text = PushDry(arena, u8);
result.len += StringFromU64(arena, (uid.hi >> 32), 16, 8).len;
return result;
}
////////////////////////////////////////////////////////////
//~ String helpers
//- Copy
String PushString(Arena *arena, String src)
{
String str = {
.len = src.len,
.text = PushStructsNoZero(arena, u8, src.len)
};
CopyBytes(str.text, src.text, src.len);
return str;
}
String PushStringToBuff(String dst, String src)
{
String result = ZI;
result.len = MinU64(dst.len, src.len);
result.text = dst.text;
CopyBytes(result.text, src.text, result.len);
return result;
}
//- Repeat
String RepeatString(Arena *arena, String src, u64 count)
{
u64 final_len = src.len * count;
u8 *final_text = PushStructsNoZero(arena, u8, final_len);
for (u64 i = 0; i < count; ++i)
{
CopyBytes(final_text + (src.len * i), src.text, src.len);
}
return (String)
{
.text = final_text,
.len = final_len
};
}
//- Concatenate
String CatString(Arena *arena, String str1, String str2)
{
String new_str = ZI;
new_str.len = str1.len + str2.len;
new_str.text = PushStructsNoZero(arena, u8, new_str.len);
CopyBytes(new_str.text, str1.text, str1.len);
CopyBytes(new_str.text + str1.len, str2.text, str2.len);
return new_str;
}
//- Split
/* `arena` is where pieces will be allocated. These strings point
* into the existing string and do not allocate any new text. */
StringArray SplitString(Arena *arena, String str, String delim)
{
StringArray pieces = ZI;
pieces.strings = PushDry(arena, String);
i64 piece_start = 0;
for (i64 i = 0; i < (i64)str.len - (i64)delim.len; ++i)
{
String cmp = ZI;
cmp.text = &str.text[i];
cmp.len = MinI64(str.len - i, delim.len);
b32 is_delimiter = MatchString(cmp, delim);
if (is_delimiter)
{
String piece = ZI;
piece.text = &str.text[piece_start];
piece.len = i - piece_start;
i += delim.len;
piece_start = i;
if (piece.len > 0)
{
*PushStructNoZero(arena, String) = piece;
++pieces.count;
}
}
}
if (piece_start < (i64)str.len)
{
String piece = ZI;
piece.text = &str.text[piece_start];
piece.len = str.len - piece_start;
*PushStructNoZero(arena, String) = piece;
++pieces.count;
}
return pieces;
}
//- Indent
/* NOTE: Really slow */
String IndentString(Arena *arena, String str, u32 indent)
{
TempArena scratch = BeginScratch(arena);
u64 final_len = 0;
u8 *final_text = PushDry(arena, u8);
StringArray split = SplitString(scratch.arena, str, Lit("\n"));
for (u64 i = 0; i < split.count; ++i)
{
String piece = split.strings[i];
for (u32 j = 0; j < indent; ++j)
{
StringFromChar(arena, ' ');
++final_len;
}
PushString(arena, piece);
final_len += piece.len;
if (i < split.count - 1)
{
StringFromChar(arena, '\n');
++final_len;
}
}
EndScratch(scratch);
return (String)
{
.len = final_len,
.text = final_text
};
}
//- Lower
String LowerString(Arena *arena, String str)
{
String result = ZI;
result.text = PushStructsNoZero(arena, u8, str.len);
result.len = str.len;
for (u64 i = 0; i < str.len; ++i)
{
u8 c = str.text[i];
if (65 <= c && c <= 90)
{
c += 32;
}
result.text[i] = c;
}
return result;
}
//- Compare
b32 MatchString(String str1, String str2)
{
b32 eq = 1;
if (str1.len == str2.len)
{
for (u64 i = 0; i < str1.len; ++i)
{
if (str1.text[i] != str2.text[i])
{
eq = 0;
break;
}
}
}
else
{
eq = 0;
}
return eq;
}
//- Match
b32 StringContains(String str, String substring)
{
if (substring.len > str.len)
{
return 0;
}
for (u64 i = 0; i <= str.len - substring.len; ++i)
{
b32 match = 1;
for (u64 j = 0; j < substring.len; ++j)
{
if (str.text[i + j] != substring.text[j])
{
match = 0;
break;
}
}
if (match)
{
return 1;
}
}
return 0;
}
b32 StringBeginsWith(String str, String substring)
{
if (str.len >= substring.len)
{
for (u64 i = 0; i < substring.len; ++i)
{
if (str.text[i] != substring.text[i])
{
return 0;
}
}
return 1;
}
return 0;
}
b32 StringEndsWith(String str, String substring)
{
if (str.len >= substring.len)
{
u64 start = str.len - substring.len;
for (u64 i = 0; i < substring.len; ++i)
{
if (str.text[start + i] != substring.text[i])
{
return 0;
}
}
return 1;
}
return 0;
}
////////////////////////////////////////////////////////////
//~ String list helpers
StringListNode *PushStringToList(Arena *arena, StringList *l, String s)
{
StringListNode *n = PushStruct(arena, StringListNode);
n->s = s;
n->prev = l->last;
if (l->last)
{
l->last->next = n;
}
else
{
l->first = n;
}
l->last = n;
++l->count;
return n;
}
String StringFromList(Arena *arena, StringList l, String separator)
{
String result = ZI;
result.text = PushDry(arena, u8);
for (StringListNode *n = l.first; n; n = n->next)
{
String s = n->s;
PushString(arena, s);
result.len += s.len;
if (n->next)
{
PushString(arena, separator);
result.len += separator.len;
}
}
return result;
}
////////////////////////////////////////////////////////////
//~ Trimming helpers
String TrimLeft(String s, String pattern)
{
String result = s;
while (StringBeginsWith(result, pattern))
{
result.text += pattern.len;
result.len -= pattern.len;
}
return result;
}
String TrimRight(String s, String pattern)
{
String result = s;
while (StringEndsWith(result, pattern))
{
result.len -= pattern.len;
}
return result;
}
String Trim(String s, String pattern)
{
return TrimLeft(TrimRight(s, pattern), pattern);
}
String TrimWhitespace(String s)
{
b32 stop = 0;
while (!stop)
{
if (StringBeginsWith(s, Lit("\n")) || StringBeginsWith(s, Lit("\r")) || StringBeginsWith(s, Lit(" ")))
{
s.text += 1;
s.len -= 1;
}
else if (StringEndsWith(s, Lit("\n")) || StringEndsWith(s, Lit("\r")) || StringEndsWith(s, Lit(" ")))
{
s.len -= 1;
}
else
{
stop = 1;
}
}
return s;
}
////////////////////////////////////////////////////////////
//~ Formatting
/* String formatting only has one format specifier: "%F". All specifier info is
* included in the arguments (instead of w/ the specifier like in printf).
*
* Example:
* FormatString(arena, Lit("Hello there %F"), FmtString(Lit("George")))
*
* NOTE: FmtEnd must be passed as the last arg in the va_list (this is
* done automatically by the `FormatString` macro).
*
* Format arguments:
* FmtChar: Format a single u8 character
* FmtString: Format a `string` struct
* FmtUint: Format a u64
* FmtSint: Format an i64
* FmtFloat: Format an f64 with DefaultFmtPrecision
* FmtFloatP: Format an f64 with specified precision
* FmtHex: Format a u64 in hexadecimal notation
* FmtPtr: Format a pointer in hexadecimal notation prefixed by "0x"
*
* FmtEnd (internal): Denote the end of the va_list
*
* TODO:
* %n equivalent? (nothing)
* %e/%E equivalent? (scientific notation of floats)
* %o equivalent? (octal representation)
*/
String FormatStringV(Arena *arena, String fmt, va_list args)
{
__prof;
u64 final_len = 0;
u8 *final_text = PushDry(arena, u8);
u8 *end = fmt.text + fmt.len;
b32 no_more_args = 0;
for (u8 *c = fmt.text; c < end; ++c)
{
u8 *next = ((c + 1) < end) ? (c + 1) : (u8 *)"\0";
/* Escape '%%' */
b32 escape = !no_more_args && *c == '%' && *next == '%';
if (escape)
{
/* Skip the escape '%' char from parsing */
++c;
}
if (!no_more_args && !escape && *c == '%' && *next == 'F')
{
String parsed_str = ZI;
/* Detect arg type and parse to string */
FmtArg arg = va_arg(args, FmtArg);
switch (arg.kind)
{
default:
{
/* Unknown format type */
Assert(0);
parsed_str = PushString(arena, Lit("<?>"));
no_more_args = 1;
} break;
case FmtKind_Char:
{
parsed_str = StringFromChar(arena, arg.value.c);
} break;
case FmtKind_String:
{
parsed_str = PushString(arena, arg.value.string);
} break;
case FmtKind_Uint:
{
parsed_str = StringFromU64(arena, arg.value.uint, 10, arg.zfill);
} break;
case FmtKind_Sint:
{
parsed_str = StringFromI64(arena, arg.value.sint, 10, arg.zfill);
} break;
case FmtKind_Hex:
{
parsed_str = StringFromU64(arena, arg.value.sint, 16, arg.zfill);
} break;
case FmtKind_Ptr:
{
parsed_str = StringFromPtr(arena, arg.value.ptr);
} break;
case FmtKind_Float:
{
parsed_str = StringFromF64(arena, arg.value.f, arg.precision);
} break;
case FmtKind_Handle:
{
parsed_str = StringFromhandle(arena, arg.value.handle.h64[0], arg.value.handle.h64[1]);
} break;
case FmtKind_Uid:
{
parsed_str = StringFromUid(arena, arg.value.uid);
} break;
case FmtKind_End:
{
/* Unexpected end. Not enough FMT args passed to function. */
Assert(0);
parsed_str = PushString(arena, Lit("<?>"));
no_more_args = 1;
} break;
}
/* Update final string len / start */
final_len += parsed_str.len;
/* Skip 'F' from parsing */
++c;
}
else
{
/* Parse character normally */
StringFromChar(arena, *c);
++final_len;
}
}
#if RtcIsEnabled
if (!no_more_args)
{
FmtArg last_arg = va_arg(args, FmtArg);
/* End arg not reached. Too many FMT values passed to function. */
Assert(last_arg.kind == FmtKind_End);
}
#endif
return (String)
{
.len = final_len,
.text = final_text
};
}
String FormatString_(Arena *arena, String fmt, ...)
{
va_list args;
va_start(args, fmt);
String new_str = FormatStringV(arena, fmt, args);
va_end(args);
return new_str;
}
////////////////////////////////////////////////////////////
//~ Unicode
//- Codepoint iteration
CodepointIter InitCodepointIter(String str)
{
return (CodepointIter)
{
.src = str
};
}
/* Returns 0 if done iterating */
b32 NextCodepoint(CodepointIter *iter)
{
if (iter->pos < iter->src.len)
{
String str_remaining = { .len = (iter->src.len - iter->pos), .text = iter->src.text + iter->pos };
Utf8DecodeResult decoded = DecodeUtf8(str_remaining);
iter->pos += decoded.advance8;
iter->codepoint = decoded.codepoint;
return 1;
}
else
{
return 0;
}
}
//- String decode
/* utf8 <- utf16 */
String StringFromString16(Arena *arena, String16 str16)
{
String result = {
.len = 0,
.text = PushDry(arena, u8)
};
u64 pos16 = 0;
while (pos16 < str16.len)
{
String16 str16_remaining = { .len = (str16.len - pos16), .text = str16.text + pos16 };
Utf16DecodeResult decoded = DecodeUtf16(str16_remaining);
Utf8EncodeResult encoded = EncodeUtf8(decoded.codepoint);
u8 *dst = PushStructsNoZero(arena, u8, encoded.count8);
CopyBytes(dst, encoded.chars8, encoded.count8);
pos16 += decoded.advance16;
result.len += encoded.count8;
}
return result;
}
/* utf8 <- utf32 */
String StringFromString32(Arena *arena, String32 str32)
{
String result = {
.len = 0,
.text = PushDry(arena, u8)
};
u64 pos32 = 0;
while (pos32 < str32.len)
{
String32 str32_remaining = { .len = (str32.len - pos32), .text = str32.text + pos32 };
Utf32DecodeResult decoded = DecodeUtf32(str32_remaining);
Utf8EncodeResult encoded = EncodeUtf8(decoded.codepoint);
u8 *dst = PushStructsNoZero(arena, u8, encoded.count8);
CopyBytes(dst, &encoded.chars8, encoded.count8);
pos32 += 1;
result.len += encoded.count8;
}
return result;
}
//- String encode
/* utf16 <- utf8 */
String16 String16FromString(Arena *arena, String str8)
{
String16 result = {
.len = 0,
.text = PushDry(arena, u16)
};
u64 pos8 = 0;
while (pos8 < str8.len)
{
String str8_remaining = { .len = (str8.len - pos8), .text = str8.text + pos8 };
Utf8DecodeResult decoded = DecodeUtf8(str8_remaining);
Utf16EncodeResult encoded = EncodeUtf16(decoded.codepoint);
u16 *dst = PushStructsNoZero(arena, u16, encoded.count16);
CopyBytes(dst, encoded.chars16, (encoded.count16 << 1));
pos8 += decoded.advance8;
result.len += encoded.count16;
}
return result;
}
/* utf32 <- utf8 */
String32 String32FromString(Arena *arena, String str8)
{
String32 result = {
.len = 0,
.text = PushDry(arena, u32)
};
u64 pos8 = 0;
while (pos8 < str8.len)
{
String str8_remaining = { .len = (str8.len - pos8), .text = str8.text + pos8 };
Utf8DecodeResult decoded = DecodeUtf8(str8_remaining);
Utf32EncodeResult encoded = EncodeUtf32(decoded.codepoint);
u32 *dst = PushStructNoZero(arena, u32);
*dst = encoded.chars32;
pos8 += decoded.advance8;
result.len += 1;
}
return result;
}
////////////////////////////////////////////////////////////
//~ Legacy null-terminated C string operations
//- Narrow C strings
u64 CstrLenNoLimit(char *cstr)
{
char *end = cstr;
if (cstr)
{
while (*end)
{
++end;
}
}
return end - cstr;
}
u64 CstrLen(char *cstr, u64 limit)
{
char *end = cstr;
if (cstr)
{
for (u64 i = 0; i < limit; ++i)
{
if (*end)
{
++end;
}
else
{
break;
}
}
}
return end - cstr;
}
char *CstrFromString(Arena *arena, String src)
{
u8 *text = PushStructsNoZero(arena, u8, src.len + 1);
CopyBytes(text, src.text, src.len);
text[src.len] = 0;
return (char *)text;
}
char *CstrFromStringToBuff(String dst_buff, String src)
{
if (dst_buff.len > 0)
{
u64 len = MinU64(src.len, dst_buff.len - 1);
CopyBytes(dst_buff.text, src.text, len);
dst_buff.text[len] = 0;
}
return (char *)dst_buff.text;
}
String StringFromCstrNoLimit(char *cstr)
{
u64 len = CstrLenNoLimit(cstr);
return (String)
{
.len = len,
.text = (u8 *)cstr
};
}
String StringFromCstr(char *cstr, u64 limit)
{
u64 len = CstrLen(cstr, limit);
return (String)
{
.text = (u8 *)cstr,
.len = len
};
}
//- Wide C strings
u64 WstrLenNoLimit(wchar_t *wstr)
{
wchar_t *end = wstr;
if (end)
{
while (*end)
{
++end;
}
}
return end - wstr;
}
u64 WstrLen(wchar_t *wstr, u64 limit)
{
wchar_t *end = wstr;
if (wstr)
{
for (u64 i = 0; i < limit; ++i)
{
if (*end)
{
++end;
}
else
{
break;
}
}
}
return end - wstr;
}
wchar_t *WstrFromString(Arena *arena, String src)
{
String16 str16 = String16FromString(arena, src);
*PushStructNoZero(arena, u16) = 0;
return (wchar_t *)str16.text;
}
wchar_t *WstrFromString16(Arena *arena, String16 src)
{
u16 *text = PushStructsNoZero(arena, u16, src.len + 1);
text[src.len] = 0;
return (wchar_t *)text;
}
String StringFromWstrNoLimit(Arena *arena, wchar_t *wstr)
{
String16 str16 = String16FromWstrNoLimit(wstr);
return StringFromString16(arena, str16);
}
String StringFromWstr(Arena *arena, wchar_t *wstr, u64 limit)
{
String16 str16 = String16FromWstr(wstr, limit);
return StringFromString16(arena, str16);
}
String16 String16FromWstrNoLimit(wchar_t *wstr)
{
u64 len = WstrLenNoLimit(wstr);
return (String16)
{
.len = len,
.text = (u16 *)wstr
};
}
String16 String16FromWstr(wchar_t *wstr, u64 limit)
{
u64 len = WstrLen(wstr, limit);
return (String16)
{
.len = len,
.text = (u16 *)wstr
};
}