//////////////////////////////////////////////////////////// //~ 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(IntChars) - 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, IntChars[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 v0, u64 v1) { String result = Zi; result.text = ArenaNext(arena, u8); result.len += PushString(arena, Lit("h")).len; result.len += StringFromUint(arena, v0, 16, 0).len; result.len += PushString(arena, Lit("x")).len; result.len += StringFromUint(arena, v1, 16, 0).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; } //////////////////////////////////////////////////////////// //~ Parsing helpers b32 BoolFromString(String str) { b32 result = 0; result = MatchString(str, Lit("true")); return result; } f64 FloatFromString(String str) { f64 result = 0; b32 ok = 1; // Eat sign u64 whole_start_idx = 0; i32 sign = 1; if (ok && str.len > 0) { if (whole_start_idx < str.len) { u8 c = str.text[whole_start_idx]; if (c == '-') { sign = -1; whole_start_idx += 1; } else if (c == '+') { whole_start_idx += 1; } } } // Find decimal place u64 frac_start_idx = whole_start_idx; for (; ok && frac_start_idx < str.len; ++frac_start_idx) { u8 c = str.text[frac_start_idx]; if (c == '.') { break; } } // Parse whole part u64 whole_part = 0; for (u64 char_idx = whole_start_idx; ok && char_idx < frac_start_idx; ++char_idx) { u8 c = str.text[char_idx]; if (c >= '0' && c <= '9') { u8 digit = c - '0'; whole_part += digit * PowU64(10, frac_start_idx - (char_idx + 1)); } else { ok = 0; } } // Parse frac part u64 frac_part = 0; for (u64 char_idx = frac_start_idx + 1; ok && char_idx < str.len; ++char_idx) { u8 c = str.text[char_idx]; if (c >= '0' && c <= '9') { u8 digit = c - '0'; frac_part += digit * PowU64(10, str.len - (char_idx + 1)); } else { ok = 0; } } if (ok) { if (frac_part != 0) { result = ((f64)whole_part + ((f64)frac_part / PowU64(10, str.len - (frac_start_idx + 1)))) * sign; } else { result = (f64)whole_part * sign; } } else { result = 0; } 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 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("\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 // // 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.h64[0], arg.value.handle.h64[1]); } 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; } //////////////////////////////////////////////////////////// //~ 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 }; }