#include API int64_t WideLength(char16_t *string) { if (!string) return 0; int64_t len = 0; while (*string++ != 0) len++; return len; } API char16_t ToLowerCase(char16_t a) { if (a >= u'A' && a <= u'Z') a += 32; return a; } API char16_t ToUpperCase(char16_t a) { if (a >= u'a' && a <= u'z') a -= 32; return a; } API bool IsWhitespace(char16_t w) { bool result = w == u'\n' || w == u' ' || w == u'\t' || w == u'\v' || w == u'\r'; return result; } API bool IsAlphabetic(char16_t a) { bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z'); return result; } API bool IsIdent(char16_t a) { bool result = (a >= u'a' && a <= u'z') || (a >= u'A' && a <= u'Z') || a == u'_'; return result; } API bool IsDigit(char16_t a) { bool result = a >= u'0' && a <= u'9'; return result; } API bool IsHexDigit(char16_t a) { bool result = a >= u'0' && a <= u'9' || a == 'a' || a == 'b' || a == 'c' || a == 'd' || a == 'e' || a == 'f'; return result; } API bool IsAlphanumeric(char16_t a) { bool result = IsDigit(a) || IsAlphabetic(a); return result; } API bool IsSymbol(char16_t w) { bool result = (w >= u'!' && w <= u'/') || (w >= u':' && w <= u'@') || (w >= u'[' && w <= u'`') || (w >= u'{' && w <= u'~'); return result; } API bool IsNonWord(char16_t w) { if (w == u'_') return false; bool result = IsSymbol(w) || IsWhitespace(w); return result; } API bool IsWord(char16_t w) { bool result = !IsNonWord(w); return result; } API bool IsBrace(char16_t c) { bool result = c == u'{' || c == u'}'; return result; } API bool IsParen(char16_t c) { bool result = c == u'(' || c == u')'; return result; } API String16 Chop(String16 a, int64_t len) { len = ClampTop(len, a.len); String16 result = {a.data, a.len - len}; return result; } API String16 Skip(String16 a, int64_t len) { len = ClampTop(len, a.len); String16 result = {a.data + len, a.len - len}; return result; } API String16 GetPostfix(String16 a, int64_t len) { len = ClampTop(len, a.len); int64_t remain_len = a.len - len; String16 result = {a.data + remain_len, len}; return result; } API String16 GetPrefix(String16 a, int64_t len) { len = ClampTop(len, a.len); String16 result = {a.data, len}; return result; } API char16_t At(String16 a, int64_t idx) { if (idx < a.len) { return a.data[idx]; } return 0; } API String16 GetSlice(String16 arr, int64_t first_index, int64_t one_past_last_index) { // Negative indexes work in python style, they return you the index counting from end of list if (one_past_last_index == SLICE_LAST) one_past_last_index = arr.len; if (one_past_last_index < 0) one_past_last_index = arr.len + one_past_last_index; if (first_index == SLICE_LAST) first_index = arr.len; if (first_index < 0) first_index = arr.len + first_index; String16 result = {arr.data, arr.len}; if (arr.len > 0) { if (one_past_last_index > first_index) { first_index = ClampTop(first_index, arr.len - 1); one_past_last_index = ClampTop(one_past_last_index, arr.len); result.data += first_index; result.len = one_past_last_index - first_index; } else { result.len = 0; } } return result; } API bool AreEqual(String16 a, String16 b, unsigned ignore_case) { if (a.len != b.len) return false; for (int64_t i = 0; i < a.len; i++) { char16_t A = a.data[i]; char16_t B = b.data[i]; if (ignore_case) { A = ToLowerCase(A); B = ToLowerCase(B); } if (A != B) return false; } return true; } API bool EndsWith(String16 a, String16 end, unsigned ignore_case) { String16 a_end = GetPostfix(a, end.len); bool result = AreEqual(end, a_end, ignore_case); return result; } API bool StartsWith(String16 a, String16 start, unsigned ignore_case) { String16 a_start = GetPrefix(a, start.len); bool result = AreEqual(start, a_start, ignore_case); return result; } API String16 Trim(String16 string) { if (string.len == 0) return string; int64_t whitespace_begin = 0; for (; whitespace_begin < string.len; whitespace_begin++) { if (!IsWhitespace(string.data[whitespace_begin])) { break; } } int64_t whitespace_end = string.len; for (; whitespace_end != whitespace_begin; whitespace_end--) { if (!IsWhitespace(string.data[whitespace_end - 1])) { break; } } if (whitespace_begin == whitespace_end) { string.len = 0; } else { string = GetSlice(string, whitespace_begin, whitespace_end); } return string; } API String16 TrimEnd(String16 string) { int64_t whitespace_end = string.len; for (; whitespace_end != 0; whitespace_end--) { if (!IsWhitespace(string.data[whitespace_end - 1])) { break; } } String16 result = GetPrefix(string, whitespace_end); return result; } API String16 Copy16(Allocator allocator, String16 string) { char16_t *copy = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (string.len + 1)); memcpy(copy, string.data, string.len * sizeof(char16_t)); copy[string.len] = 0; String16 result = {copy, string.len}; return result; } API String16 Copy16(Allocator allocator, char16_t *string) { return Copy16(allocator, {string, (int64_t)WideLength(string)}); } API void NormalizePathInPlace(String16 s) { for (int64_t i = 0; i < s.len; i++) { if (s.data[i] == u'\\') s.data[i] = u'/'; } } API String16 NormalizePath(Allocator allocator, String16 s) { String16 copy = Copy16(allocator, s); NormalizePathInPlace(copy); return copy; } API bool Seek(String16 string, String16 find, int64_t *index_out, SeekFlag flags) { bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false; bool result = false; if (flags & SeekFlag_MatchFindLast) { for (int64_t i = string.len; i != 0; i--) { int64_t index = i - 1; String16 substring = GetSlice(string, index, index + find.len); if (AreEqual(substring, find, ignore_case)) { bool ok_boundary = true; if (flags & SeekFlag_WordBoundary) { String16 left = GetSlice(string, i - 1, i); String16 right = GetSlice(string, i + find.len, i + find.len + 1); if (left.len != 0 && !IsNonWord(left.data[0])) { ok_boundary = false; } if (right.len != 0 && !IsNonWord(right.data[0])) { ok_boundary = false; } } if (ok_boundary) { if (index_out) { *index_out = index; } result = true; break; } } } } else { for (int64_t i = 0; i < string.len; i++) { String16 substring = GetSlice(string, i, i + find.len); if (AreEqual(substring, find, ignore_case)) { bool ok_boundary = true; if (flags & SeekFlag_WordBoundary) { String16 left = GetSlice(string, i - 1, i); String16 right = GetSlice(string, i + find.len, i + find.len + 1); if (left.len != 0 && !IsNonWord(left.data[0])) { ok_boundary = false; } if (right.len != 0 && !IsNonWord(right.data[0])) { ok_boundary = false; } } if (ok_boundary) { if (index_out) { *index_out = i; } result = true; break; } } } } return result; } API String16 CutLastSlash(String16 *s) { String16 result = *s; Seek(*s, u"/", &s->len, SeekFlag_MatchFindLast); result = Skip(result, s->len); return result; } API String16 ChopLastSlash(String16 s) { String16 result = s; Seek(s, u"/", &result.len, SeekFlag_MatchFindLast); return result; } API String16 ChopLastPeriod(String16 s) { String16 result = s; Seek(s, u".", &result.len, SeekFlag_MatchFindLast); return result; } API String16 SkipToLastSlash(String16 s) { int64_t pos; String16 result = s; if (Seek(s, u"/", &pos, SeekFlag_MatchFindLast)) { result = Skip(result, pos + 1); } return result; } API String16 SkipToLastPeriod(String16 s) { int64_t pos; String16 result = s; if (Seek(s, u".", &pos, SeekFlag_MatchFindLast)) { result = Skip(result, pos + 1); } return result; } API String16 CutPrefix(String16 *string, int64_t len) { String16 result = GetPrefix(*string, len); *string = Skip(*string, len); return result; } API String16 CutPostfix(String16 *string, int64_t len) { String16 result = GetPostfix(*string, len); *string = Chop(*string, len); return result; } API String16 Format16V(Allocator allocator, const char *data, va_list args1) { Scratch scratch(allocator); va_list args2; va_copy(args2, args1); int64_t len = stbsp_vsnprintf(0, 0, data, args2); va_end(args2); char *result = (char *)AllocSize(scratch, sizeof(char) * (len + 1)); stbsp_vsnprintf(result, (int)(len + 1), data, args1); String res = {result, len}; String16 res16 = ToString16(allocator, res); return res16; } API String16 Format16(Allocator allocator, const char *data, ...) { Scratch scratch(allocator); STRING_FORMAT(scratch, data, result); String16 result16 = ToString16(allocator, result); return result16; } API Array Split(Allocator allocator, String16 string, String16 delimiter) { Array result = {allocator}; int64_t index = 0; while (Seek(string, delimiter, &index)) { String16 before_match = {string.data, index}; Add(&result, before_match); string = Skip(string, index + delimiter.len); } Add(&result, string); return result; } API String16 Merge(Allocator allocator, Array list, String16 separator) { int64_t char_count = 0; For(list) char_count += it.len; if (char_count == 0) return {}; int64_t node_count = list.len; int64_t base_size = (char_count + 1); int64_t sep_size = (node_count - 1) * separator.len; int64_t size = base_size + sep_size; char16_t *buff = (char16_t *)AllocSize(allocator, sizeof(char16_t) * (size + 1)); String16 string = {buff, 0}; For(list) { Assert(string.len + it.len <= size); memcpy(string.data + string.len, it.data, it.len * sizeof(char16_t)); string.len += it.len; if (!IsLast(list, it)) { memcpy(string.data + string.len, separator.data, separator.len * sizeof(char16_t)); string.len += separator.len; } } Assert(string.len == size - 1); string.data[size] = 0; return string; } API Int GetSize(Array array) { Int result = 0; For (array) result += it.len; return result; } API String16 SkipIntEx(String16 *string) { String16 col = {string->data, 0}; if (At(*string, 0) == '-') { col.len += 1; *string = Skip(*string, 1); } for (int64_t i = 0; i < string->len; i += 1) { if (IsDigit(string->data[i])) { col.len += 1; } else { break; } } *string = Skip(*string, col.len); return col; } API Int SkipInt(String16 *string) { String16 col = SkipIntEx(string); if (col.len == 0) return 0; Scratch scratch; String num_string = ToString(scratch, col); Int result = strtoll(num_string.data, NULL, 10); return result; } API String16 SkipFloatEx(String16 *string) { String16 col = {string->data, 0}; if (At(*string, 0) == '-') { col.len += 1; *string = Skip(*string, 1); } for (int64_t i = 0; i < string->len; i += 1) { if (IsDigit(string->data[i]) || string->data[i] == u'.') { col.len += 1; } else { break; } } *string = Skip(*string, col.len); return col; } API Float SkipFloat(String16 *string) { String16 col = SkipFloatEx(string); if (col.len == 0) return 0; Scratch scratch; String num_string = ToString(scratch, col); Float result = strtod(num_string.data, NULL); return result; } API String16 SkipUntil(String16 *string, String16 str) { String16 begin = *string; begin.len = 0; for (; string->len; begin.len += 1) { String16 match = GetPrefix(*string, str.len); if (StartsWith(match, str)) break; *string = Skip(*string, 1); } return begin; } API String16 SkipWhitespace(String16 *string) { String16 begin = {string->data, 0}; for (;string->len;) { if (!IsWhitespace(At(*string, 0))) { break; } *string = Skip(*string, 1); begin.len += 1; } return begin; } String16 SkipIdent(String16 *string) { String16 begin = {string->data, 0}; if (IsIdent(At(*string, 0))) { for (;string->len;) { char16_t c = At(*string, 0); if (!IsIdent(c) && !IsDigit(c)) { break; } *string = Skip(*string, 1); begin.len += 1; } } return begin; } String16 SkipString(String16 *string) { String16 saved_string = *string; char16_t c = At(*string, 0); String16 q = {&c, 1}; if (c == u'"' || c == u'\'') { *string = Skip(*string, 1); String16 quote = SkipUntil(string, q); if (At(*string, 0) != c) { *string = saved_string; return {}; } return quote; } return {}; } bool MatchIdent(String16 *string, String16 expect) { String16 copy = *string; String16 ident = SkipIdent(©); if (ident == expect) { *string = copy; return true; } return false; } API bool Chop(String16 *string, String16 ending) { if (EndsWith(*string, ending)) { *string = Chop(*string, ending.len); return true; } return false; } // chop this - 324 API String16 ChopNumberEx(String16 *string) { String16 col = {}; for (int64_t i = string->len - 1; i >= 0; i -= 1) { if (IsDigit(string->data[i])) { col.data = string->data + i; col.len += 1; } else { break; } } *string = Chop(*string, col.len); return col; } API Int ChopNumber(String16 *string) { Scratch scratch; String16 col = ChopNumberEx(string); if (col.len == 0) return -1; String num_string = ToString(scratch, col); Int result = strtoll(num_string.data, NULL, 10) - 1; return result; } API String16 Concat(Allocator allocator, String16 a, String16 b) { char16_t *p = AllocArray(allocator, char16_t, a.len + b.len + 1); MemoryCopy(p, a.data, sizeof(char16_t) * a.len); MemoryCopy(p + a.len, b.data, sizeof(char16_t) * b.len); String16 result = {p, a.len + b.len}; result.data[result.len] = 0; return result; }