1223 lines
27 KiB
C
1223 lines
27 KiB
C
////////////////////////////////////////////////////////////
|
|
//~ Conversion helpers
|
|
|
|
String StringFromBool(Arena *arena, b32 b)
|
|
{
|
|
return b ? PushString(arena, Lit("true")) : PushString(arena, Lit("false"));
|
|
}
|
|
|
|
String StringFromChar(Arena *arena, char c)
|
|
{
|
|
u8 *dst = PushStructNoZero(arena, u8);
|
|
*dst = c;
|
|
return (String)
|
|
{
|
|
.len = 1,
|
|
.text = dst
|
|
};
|
|
}
|
|
|
|
String StringFromUint(Arena *arena, u64 n, u64 base, u64 zfill)
|
|
{
|
|
// Base too large
|
|
Assert(base <= (countof(Base16Chars) - 1));
|
|
String result = Zi;
|
|
TempArena scratch = BeginScratch(arena);
|
|
{
|
|
// Build backwards text starting from least significant digit
|
|
u8 *backwards_text = ArenaNext(scratch.arena, u8);
|
|
do
|
|
{
|
|
StringFromChar(scratch.arena, Base16Chars[n % base]);
|
|
++result.len;
|
|
n /= base;
|
|
} while (n > 0);
|
|
|
|
// Fill zeroes
|
|
while (result.len < zfill)
|
|
{
|
|
StringFromChar(scratch.arena, '0');
|
|
++result.len;
|
|
}
|
|
|
|
// Reverse text into final string
|
|
result.text = PushStructsNoZero(arena, u8, result.len);
|
|
for (u64 i = 0; i < result.len; ++i)
|
|
{
|
|
result.text[i] = backwards_text[result.len - i - 1];
|
|
}
|
|
|
|
}
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
String StringFromUints(Arena *arena, u64 uints_count, u64 *uints, u64 base, u64 zfill)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
result.len += StringFromChar(arena, '(').len;
|
|
for (u64 uint_idx = 0; uint_idx < uints_count; ++uint_idx)
|
|
{
|
|
result.len += StringFromUint(arena, uints[uint_idx], base, zfill).len;
|
|
if (uint_idx + 1 < uints_count)
|
|
{
|
|
result.len += PushString(arena, Lit(", ")).len;
|
|
}
|
|
}
|
|
result.len += StringFromChar(arena, ')').len;
|
|
return result;
|
|
}
|
|
|
|
String StringFromSint(Arena *arena, i64 n, u64 base, u64 zfill)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
if (n < 0)
|
|
{
|
|
result.len += StringFromChar(arena, '-').len;
|
|
n = -n;
|
|
}
|
|
result.len += StringFromUint(arena, (u64)n, base, zfill).len;
|
|
return result;
|
|
}
|
|
|
|
String StringFromSints(Arena *arena, u64 sints_count, i64 *sints, u64 base, u64 zfill)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
result.len += StringFromChar(arena, '(').len;
|
|
for (u64 sint_idx = 0; sint_idx < sints_count; ++sint_idx)
|
|
{
|
|
result.len += StringFromSint(arena, sints[sint_idx], base, zfill).len;
|
|
if (sint_idx + 1 < sints_count)
|
|
{
|
|
result.len += PushString(arena, Lit(", ")).len;
|
|
}
|
|
}
|
|
result.len += StringFromChar(arena, ')').len;
|
|
return result;
|
|
}
|
|
|
|
String StringFromFloat(Arena *arena, f64 src, u32 precision)
|
|
{
|
|
String result = Zi;
|
|
precision = MinU32(precision, 16);
|
|
|
|
if (IsNan(src))
|
|
{
|
|
result = PushString(arena, Lit("NaN"));
|
|
}
|
|
else if (IsInf(src))
|
|
{
|
|
if (src >= 0)
|
|
{
|
|
result = PushString(arena, Lit("inf"));
|
|
}
|
|
else
|
|
{
|
|
result = PushString(arena, Lit("-inf"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.text = ArenaNext(arena, u8);
|
|
|
|
u64 p = PowU64(10, precision);
|
|
f64 multiplied = RoundF64(src * p);
|
|
|
|
i32 sign = (src >= 0) - (src < 0);
|
|
u64 part_whole = TruncF64(AbsF64(multiplied) / p);
|
|
|
|
u64 part_frac = RoundF64(AbsF64(AbsF64(src) - part_whole) * p);
|
|
|
|
// Push sign
|
|
if (sign < 0 && (part_whole != 0 || part_frac != 0))
|
|
{
|
|
result.len += StringFromChar(arena, '-').len;
|
|
}
|
|
|
|
// Push whole part
|
|
result.len += StringFromUint(arena, part_whole, 10, 0).len;
|
|
|
|
// Push frac part
|
|
if (part_frac != 0)
|
|
{
|
|
result.len += StringFromChar(arena, '.').len;
|
|
i64 frac_start_idx = result.len;
|
|
result.len += StringFromUint(arena, part_frac, 10, precision).len;
|
|
|
|
// Remove trailing zeroes
|
|
for (i64 char_idx = result.len - 1; char_idx > frac_start_idx; --char_idx)
|
|
{
|
|
u8 c = result.text[char_idx];
|
|
if (c == '0')
|
|
{
|
|
result.len -= 1;
|
|
PopBytesNoCopy(arena, 1);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
String StringFromFloats(Arena *arena, u64 floats_count, f64 *floats, u32 precision)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
result.len += StringFromChar(arena, '(').len;
|
|
for (u64 float_idx = 0; float_idx < floats_count; ++float_idx)
|
|
{
|
|
result.len += StringFromFloat(arena, floats[float_idx], precision).len;
|
|
if (float_idx + 1 < floats_count)
|
|
{
|
|
result.len += PushString(arena, Lit(", ")).len;
|
|
}
|
|
}
|
|
result.len += StringFromChar(arena, ')').len;
|
|
return result;
|
|
}
|
|
|
|
String StringFromPtr(Arena *arena, void *ptr)
|
|
{
|
|
String prepend = PushString(arena, Lit("0x"));
|
|
String uint_str = StringFromUint(arena, (u64)ptr, 16, sizeof(ptr));
|
|
return (String)
|
|
{
|
|
.len = prepend.len + uint_str.len,
|
|
.text = prepend.text
|
|
};
|
|
}
|
|
|
|
String StringFromhandle(Arena *arena, u64 v)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
result.len += PushString(arena, Lit("[")).len;
|
|
result.len += StringFromUint(arena, (v >> 32), 16, 0).len;
|
|
result.len += PushString(arena, Lit("]")).len;
|
|
return result;
|
|
}
|
|
|
|
String StringFromUid(Arena *arena, Uid uid)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
result.len += StringFromUint(arena, (uid.hi >> 32), 16, 8).len;
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ String helpers
|
|
|
|
String PushString(Arena *arena, String src)
|
|
{
|
|
String result = Zi;
|
|
result.len = src.len;
|
|
result.text = PushStructsNoZero(arena, u8, src.len);
|
|
CopyBytes(result.text, src.text, src.len);
|
|
return result;
|
|
}
|
|
|
|
String CopyString(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;
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// `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 = ArenaNext(arena, String);
|
|
i64 piece_start = 0;
|
|
for (i64 i = 0; i < (i64)str.len - (i64)delim.len;)
|
|
{
|
|
String cmp = Zi;
|
|
cmp.text = &str.text[i];
|
|
cmp.len = MinI64(str.len - i, delim.len);
|
|
|
|
if (MatchString(cmp, delim))
|
|
{
|
|
String piece = Zi;
|
|
piece.text = &str.text[piece_start];
|
|
piece.len = i - piece_start;
|
|
*PushStructNoZero(arena, String) = piece;
|
|
++pieces.count;
|
|
i += delim.len;
|
|
piece_start = i;
|
|
}
|
|
else
|
|
{
|
|
i += 1;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
String ReplaceString(Arena *arena, String str, String old_pattern, String new_pattern)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
i64 piece_start = 0;
|
|
for (i64 i = 0; i < (i64)str.len - (i64)old_pattern.len;)
|
|
{
|
|
String cmp = Zi;
|
|
cmp.text = &str.text[i];
|
|
cmp.len = MinI64(str.len - i, old_pattern.len);
|
|
if (MatchString(cmp, old_pattern))
|
|
{
|
|
String piece = Zi;
|
|
piece.text = &str.text[piece_start];
|
|
piece.len = i - piece_start;
|
|
if (piece.len > 0)
|
|
{
|
|
result.len += PushString(arena, piece).len;
|
|
}
|
|
result.len += PushString(arena, new_pattern).len;
|
|
i += old_pattern.len;
|
|
piece_start = i;
|
|
}
|
|
else
|
|
{
|
|
i += 1;
|
|
}
|
|
}
|
|
if (piece_start < (i64)str.len)
|
|
{
|
|
String piece = Zi;
|
|
piece.text = &str.text[piece_start];
|
|
piece.len = str.len - piece_start;
|
|
result.len += PushString(arena, piece).len;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// NOTE: Really slow
|
|
String IndentString(Arena *arena, String str, u32 indent)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
|
|
u64 final_len = 0;
|
|
u8 *final_text = ArenaNext(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
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 StringFromArray(Arena *arena, StringArray a)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
for (u64 string_idx = 0; string_idx < a.count; ++string_idx)
|
|
{
|
|
result.len += PushString(arena, a.strings[string_idx]).len;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
String PathFromString(Arena *arena, String str, u8 path_delimiter)
|
|
{
|
|
String result = Zi;
|
|
result = PushString(arena, str);
|
|
for (u64 char_idx = 0; char_idx < result.len; ++char_idx)
|
|
{
|
|
u8 c = result.text[char_idx];
|
|
if ((c == '\\' || c == '/') && c != path_delimiter)
|
|
{
|
|
result.text[char_idx] = path_delimiter;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Hash helpers
|
|
|
|
u64 HashStringEx(u64 seed, String str)
|
|
{
|
|
u64 result = HashFnv64(seed, str);
|
|
result = MixU64(result);
|
|
return result;
|
|
}
|
|
|
|
u64 HashString(String str)
|
|
{
|
|
return HashStringEx(Fnv64Basis, str);
|
|
}
|
|
|
|
u64 HashF_(String fmt, ...)
|
|
{
|
|
u64 result = 0;
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
{
|
|
String str = FormatString(scratch.arena, fmt, FmtArgsFromVaList(scratch.arena, args));
|
|
result = HashString(str);
|
|
}
|
|
va_end(args);
|
|
}
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ 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 = ArenaNext(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(" ")) || StringBeginsWith(s, Lit("\t")) || StringBeginsWith(s, Lit("\n")) || StringBeginsWith(s, Lit("\r")))
|
|
{
|
|
s.text += 1;
|
|
s.len -= 1;
|
|
}
|
|
else if (StringEndsWith(s, Lit(" ")) || StringEndsWith(s, Lit("\t")) || StringEndsWith(s, Lit("\n")) || StringEndsWith(s, Lit("\r")))
|
|
{
|
|
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
|
|
//
|
|
// 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
|
|
// 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
|
|
//
|
|
|
|
String FormatString(Arena *arena, String fmt, FmtArgArray args)
|
|
{
|
|
String result = Zi;
|
|
result.text = ArenaNext(arena, u8);
|
|
|
|
u8 *end = fmt.text + fmt.len;
|
|
b32 no_more_valid_args = 0;
|
|
u64 arg_idx = 0;
|
|
for (u8 *c = fmt.text; c < end; ++c)
|
|
{
|
|
u8 *next = ((c + 1) < end) ? (c + 1) : (u8 *)"\0";
|
|
|
|
// Escape '%%'
|
|
b32 escape = !no_more_valid_args && *c == '%' && *next == '%';
|
|
if (escape)
|
|
{
|
|
// Skip the escaped '%' char from parsing
|
|
++c;
|
|
}
|
|
|
|
if (!no_more_valid_args && !escape && *c == '%' && *next == 'F')
|
|
{
|
|
String parsed_arg = Zi;
|
|
|
|
FmtArg arg = Zi;
|
|
if (arg_idx < args.count)
|
|
{
|
|
arg = args.args[arg_idx];
|
|
++arg_idx;
|
|
}
|
|
else
|
|
{
|
|
no_more_valid_args = 1;
|
|
}
|
|
|
|
switch (arg.kind)
|
|
{
|
|
default:
|
|
{
|
|
// Unknown format type
|
|
Assert(0);
|
|
parsed_arg = PushString(arena, Lit("<?>"));
|
|
no_more_valid_args = 1;
|
|
} break;
|
|
|
|
case FmtArgKind_Char:
|
|
{
|
|
parsed_arg = StringFromChar(arena, arg.value.c);
|
|
} break;
|
|
|
|
case FmtArgKind_String:
|
|
{
|
|
parsed_arg = PushString(arena, arg.value.string);
|
|
} break;
|
|
|
|
case FmtArgKind_Uint:
|
|
{
|
|
parsed_arg = StringFromUint(arena, arg.value.uints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Uint2:
|
|
{
|
|
parsed_arg = StringFromUints(arena, 2, &arg.value.uints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Uint3:
|
|
{
|
|
parsed_arg = StringFromUints(arena, 3, &arg.value.uints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Uint4:
|
|
{
|
|
parsed_arg = StringFromUints(arena, 4, &arg.value.uints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Sint:
|
|
{
|
|
parsed_arg = StringFromSint(arena, arg.value.sints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Sint2:
|
|
{
|
|
parsed_arg = StringFromSints(arena, 2, &arg.value.sints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Sint3:
|
|
{
|
|
parsed_arg = StringFromSints(arena, 3, &arg.value.sints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Sint4:
|
|
{
|
|
parsed_arg = StringFromSints(arena, 4, &arg.value.sints.x, 10, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Float:
|
|
{
|
|
parsed_arg = StringFromFloat(arena, arg.value.floats.x, arg.p);
|
|
} break;
|
|
|
|
case FmtArgKind_Float2:
|
|
{
|
|
parsed_arg = StringFromFloats(arena, 2, &arg.value.floats.x, arg.p);
|
|
} break;
|
|
|
|
case FmtArgKind_Float3:
|
|
{
|
|
parsed_arg = StringFromFloats(arena, 3, &arg.value.floats.x, arg.p);
|
|
} break;
|
|
|
|
case FmtArgKind_Float4:
|
|
{
|
|
parsed_arg = StringFromFloats(arena, 4, &arg.value.floats.x, arg.p);
|
|
} break;
|
|
|
|
case FmtArgKind_Hex:
|
|
{
|
|
parsed_arg = StringFromUint(arena, arg.value.uints.x, 16, arg.z);
|
|
} break;
|
|
|
|
case FmtArgKind_Ptr:
|
|
{
|
|
parsed_arg = StringFromPtr(arena, arg.value.ptr);
|
|
} break;
|
|
|
|
case FmtArgKind_Handle:
|
|
{
|
|
parsed_arg = StringFromhandle(arena, arg.value.handle);
|
|
} break;
|
|
|
|
case FmtArgKind_Uid:
|
|
{
|
|
parsed_arg = StringFromUid(arena, arg.value.uid);
|
|
} break;
|
|
|
|
case FmtArgKind_End:
|
|
{
|
|
// Unexpected end. Not enough FMT args passed to function.
|
|
Assert(0);
|
|
parsed_arg = PushString(arena, Lit("<?>"));
|
|
no_more_valid_args = 1;
|
|
} break;
|
|
}
|
|
|
|
// Update final string len / start
|
|
result.len += parsed_arg.len;
|
|
|
|
// Skip 'F' from parsing
|
|
++c;
|
|
}
|
|
else
|
|
{
|
|
// Parse character normally
|
|
StringFromChar(arena, *c);
|
|
++result.len;
|
|
}
|
|
}
|
|
|
|
if (IsRtcEnabled)
|
|
{
|
|
if (!no_more_valid_args)
|
|
{
|
|
FmtArg last_arg = Zi;
|
|
if (arg_idx < args.count)
|
|
{
|
|
last_arg = args.args[arg_idx];
|
|
// End arg not reached. Too many args supplied.
|
|
Assert(last_arg.kind == FmtArgKind_End);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
String StringF_(Arena *arena, String fmt, ...)
|
|
{
|
|
String result = Zi;
|
|
TempArena scratch = BeginScratch(arena);
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
result = FormatString(arena, fmt, FmtArgsFromVaList(scratch.arena, args));
|
|
va_end(args);
|
|
}
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
FmtArgArray FmtArgsFromVaList(Arena *arena, va_list args)
|
|
{
|
|
FmtArgArray result = Zi;
|
|
result.args = ArenaNext(arena, FmtArg);
|
|
{
|
|
b32 done = 0;
|
|
while (!done)
|
|
{
|
|
FmtArg arg = va_arg(args, FmtArg);
|
|
*PushStructNoZero(arena, FmtArg) = arg;
|
|
++result.count;
|
|
switch (arg.kind)
|
|
{
|
|
default:
|
|
{
|
|
// End/Invalid arg reached
|
|
done = 1;
|
|
} break;
|
|
|
|
case FmtArgKind_Char:
|
|
case FmtArgKind_String:
|
|
case FmtArgKind_Uint:
|
|
case FmtArgKind_Uint2:
|
|
case FmtArgKind_Uint3:
|
|
case FmtArgKind_Uint4:
|
|
case FmtArgKind_Sint:
|
|
case FmtArgKind_Sint2:
|
|
case FmtArgKind_Sint3:
|
|
case FmtArgKind_Sint4:
|
|
case FmtArgKind_Float:
|
|
case FmtArgKind_Float2:
|
|
case FmtArgKind_Float3:
|
|
case FmtArgKind_Float4:
|
|
case FmtArgKind_Hex:
|
|
case FmtArgKind_Ptr:
|
|
case FmtArgKind_Uid:
|
|
case FmtArgKind_Handle:
|
|
{
|
|
// Continue
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Unicode
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// utf8 <- utf16
|
|
String StringFromString16(Arena *arena, String16 str16)
|
|
{
|
|
String result = {
|
|
.len = 0,
|
|
.text = ArenaNext(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 = ArenaNext(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;
|
|
}
|
|
|
|
// utf16 <- utf8
|
|
String16 String16FromString(Arena *arena, String str8)
|
|
{
|
|
String16 result = {
|
|
.len = 0,
|
|
.text = ArenaNext(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 = ArenaNext(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;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Base64
|
|
|
|
u64 Base64LenFromStringLen(u64 len)
|
|
{
|
|
return (len * 4 + 2) / 3;
|
|
}
|
|
|
|
u64 StringLenFromBase64Len(u64 len)
|
|
{
|
|
return len * 3 / 4;
|
|
}
|
|
|
|
String Base64FromString(Arena *arena, String str)
|
|
{
|
|
String result = Zi;
|
|
PERSIST Readonly u8 to_base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
result.len = Base64LenFromStringLen(str.len);
|
|
result.text = PushStructsNoZero(arena, u8, result.len);
|
|
u64 src_byte_pos = 0;
|
|
u64 out_byte_pos = 0;
|
|
while (src_byte_pos < str.len)
|
|
{
|
|
u32 chunk = 0;
|
|
chunk |= str.text[src_byte_pos] << 16;
|
|
if (src_byte_pos + 1 < str.len) chunk |= str.text[src_byte_pos + 1] << 8;
|
|
if (src_byte_pos + 2 < str.len) chunk |= str.text[src_byte_pos + 2] << 0;
|
|
result.text[out_byte_pos + 0] = to_base64[(chunk >> 18) & 0x3F];
|
|
result.text[out_byte_pos + 1] = to_base64[(chunk >> 12) & 0x3F];
|
|
result.text[out_byte_pos + 2] = to_base64[(chunk >> 6) & 0x3F];
|
|
result.text[out_byte_pos + 3] = to_base64[(chunk >> 0) & 0x3F];
|
|
src_byte_pos += 3;
|
|
out_byte_pos += 4;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
String StringFromBase64(Arena *arena, String str)
|
|
{
|
|
String result = Zi;
|
|
result.len = StringLenFromBase64Len(str.len);
|
|
result.text = PushStructsNoZero(arena, u8, result.len);
|
|
PERSIST Readonly u8 from_base64[256] = {['A']=0,['B']=1,['C']=2,['D']=3,['E']=4,['F']=5,['G']=6,['H']=7,['I']=8,['J']=9,['K']=10,['L']=11,['M']=12,['N']=13,['O']=14,['P']=15,['Q']=16,['R']=17,['S']=18,['T']=19,['U']=20,['V']=21,['W']=22,['X']=23,['Y']=24,['Z']=25,['a']=26,['b']=27,['c']=28,['d']=29,['e']=30,['f']=31,['g']=32,['h']=33,['i']=34,['j']=35,['k']=36,['l']=37,['m']=38,['n']=39,['o']=40,['p']=41,['q']=42,['r']=43,['s']=44,['t']=45,['u']=46,['v']=47,['w']=48,['x']=49,['y']=50,['z']=51,['0']=52,['1']=53,['2']=54,['3']=55,['4']=56,['5']=57,['6']=58,['7']=59,['8']=60,['9']=61,['-']=62,['_']=63};
|
|
u64 src_byte_pos = 0;
|
|
u64 out_byte_pos = 0;
|
|
while (src_byte_pos < str.len)
|
|
{
|
|
u32 chunk = 0;
|
|
chunk |= (from_base64[str.text[src_byte_pos + 0]]) << 18;
|
|
chunk |= (from_base64[str.text[src_byte_pos + 1]]) << 12;
|
|
chunk |= (from_base64[str.text[src_byte_pos + 2]]) << 6;
|
|
chunk |= (from_base64[str.text[src_byte_pos + 3]]) << 0;
|
|
result.text[out_byte_pos + 0] = (chunk >> 16) & 0xFF;
|
|
result.text[out_byte_pos + 1] = (chunk >> 8) & 0xFF;
|
|
result.text[out_byte_pos + 2] = (chunk >> 0) & 0xFF;
|
|
src_byte_pos += 4;
|
|
out_byte_pos += 3;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Null-terminated 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
|
|
};
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|