2023-06-21 16:59:36 +00:00
|
|
|
#define STB_SPRINTF_IMPLEMENTATION
|
2023-12-07 15:50:57 +00:00
|
|
|
#include "../third_party/stb_sprintf.h"
|
2023-06-17 17:00:55 +00:00
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//~ sixten: Char funcitons
|
2023-06-17 17:00:55 +00:00
|
|
|
|
|
|
|
inline b32 IsWhitespace(char C)
|
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
b32 Result = ((C == ' ') ||
|
|
|
|
(C == '\n') ||
|
|
|
|
(C == '\t') ||
|
|
|
|
(C == '\r'));
|
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-06-19 17:12:26 +00:00
|
|
|
inline b32 IsDigit(char C)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
b32 Result = ((C >= '0') && (C <= '9'));
|
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-06-19 17:12:26 +00:00
|
|
|
inline b32 IsLetter(char C)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
b32 Result = ((C >= 'A') && (C <= 'Z')) || ((C >= 'a') && (C <= 'z'));
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-17 17:00:55 +00:00
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//~ sixten: String functions
|
|
|
|
|
|
|
|
//- sixten: Basic constructors
|
|
|
|
|
|
|
|
inline string MakeString(u8 *Data, s64 Count)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
string Result = {Count, Data};
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-08-22 03:19:51 +00:00
|
|
|
inline string MakeString(u8 *Start, u8 *End)
|
|
|
|
{
|
|
|
|
string Result = {(s64)(End-Start), Start};
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
inline string MakeString(char *CString)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
string Result = {StringLength(CString), (u8 *)CString};
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-08-22 03:19:51 +00:00
|
|
|
inline string16 MakeString16(u16 *Data, s64 Count)
|
|
|
|
{
|
|
|
|
string16 Result = {Count, Data};
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//- sixten: Equality
|
|
|
|
|
|
|
|
static b32 AreEqual(string A, string B)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
|
|
|
b32 Result = false;
|
|
|
|
if(A.Count == B.Count)
|
|
|
|
{
|
|
|
|
Result = true;
|
|
|
|
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < A.Count;
|
|
|
|
++Index)
|
|
|
|
{
|
|
|
|
if(A.Data[Index] != B.Data[Index])
|
|
|
|
{
|
|
|
|
Result = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Result);
|
2023-06-18 12:18:34 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//- sixten: Substring
|
|
|
|
|
|
|
|
static string Substring(string String, range1_s64 Range)
|
|
|
|
{
|
|
|
|
string Result = MakeString(String.Data + Range.Min, DimOfRange(Range));
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static string Prefix(string String, s64 Count)
|
|
|
|
{
|
|
|
|
range1_s64 Range = Range1S64(0, Count);
|
|
|
|
string Result = Substring(String, Range);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static string Suffix(string String, s64 Count)
|
|
|
|
{
|
|
|
|
range1_s64 Range = Range1S64(String.Count - Count, String.Count);
|
|
|
|
string Result = Substring(String, Range);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
//- sixten: Hashing
|
|
|
|
|
|
|
|
static u64 HashString(string String)
|
|
|
|
{
|
|
|
|
u64 Result = 5731;
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < String.Count;
|
|
|
|
++Index)
|
|
|
|
{
|
|
|
|
Result += String.Data[Index];
|
|
|
|
Result ^= Result << 13;
|
|
|
|
Result ^= Result >> 7;
|
|
|
|
Result ^= Result << 17;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
//- sixten: Searching
|
|
|
|
|
|
|
|
static s64 FirstIndexOf(string String, char Char)
|
2023-06-18 12:18:34 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
s64 Result = -1;
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < String.Count;
|
|
|
|
++Index)
|
2023-06-18 12:18:34 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
if(String.Data[Index] == Char)
|
|
|
|
{
|
|
|
|
Result = Index;
|
|
|
|
break;
|
|
|
|
}
|
2023-06-18 12:18:34 +00:00
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
static s64 LastIndexOf(string String, char Char)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
s64 Result = -1;
|
|
|
|
for(s64 Index = String.Count-1;
|
|
|
|
Index >= 0;
|
|
|
|
--Index)
|
2023-06-18 12:18:34 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
if(String.Data[Index] == Char)
|
|
|
|
{
|
|
|
|
Result = Index;
|
|
|
|
break;
|
|
|
|
}
|
2023-06-18 12:18:34 +00:00
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
static s64 FirstIndexOf(string String, string Sub)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
s64 Result = -1;
|
2023-06-21 16:59:36 +00:00
|
|
|
if(String.Count >= Sub.Count)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < String.Count - Sub.Count;
|
|
|
|
++Index)
|
|
|
|
{
|
|
|
|
string ToCheck = Substring(String, Range1S64(Index, Index + Sub.Count));
|
|
|
|
if(AreEqual(ToCheck, Sub))
|
|
|
|
{
|
|
|
|
Result = Index;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static s64 LastIndexOf(string String, string Sub)
|
|
|
|
{
|
|
|
|
s64 Result = -1;
|
|
|
|
if(String.Count >= Sub.Count)
|
|
|
|
{
|
|
|
|
for(s64 Index = String.Count - Sub.Count - 1;
|
2023-06-19 17:12:26 +00:00
|
|
|
Index >= 0;
|
|
|
|
--Index)
|
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
string ToCheck = Substring(String, Range1S64(Index, Index + Sub.Count));
|
|
|
|
if(AreEqual(ToCheck, Sub))
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
|
|
|
Result = Index;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-21 16:59:36 +00:00
|
|
|
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//- sixten: Allocation
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string PushString(arena *Arena, string String)
|
2023-06-21 16:59:36 +00:00
|
|
|
{
|
|
|
|
string Result;
|
2023-07-19 15:09:41 +00:00
|
|
|
Result.Data = PushArrayNoClear(Arena, u8, String.Count);
|
2023-06-21 16:59:36 +00:00
|
|
|
Result.Count = String.Count;
|
|
|
|
Copy(Result.Data, String.Data, String.Count);
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string PushFormatVariadic(arena *Arena, char *Format, va_list Arguments)
|
2023-06-21 16:59:36 +00:00
|
|
|
{
|
|
|
|
va_list ArgumentsCopy;
|
|
|
|
va_copy(ArgumentsCopy, Arguments);
|
|
|
|
|
|
|
|
string Result;
|
|
|
|
Result.Count = stbsp_vsnprintf(0, 0, Format, ArgumentsCopy);
|
2023-07-19 15:09:41 +00:00
|
|
|
Result.Data = PushArrayNoClear(Arena, u8, Result.Count + 1);
|
2023-06-21 16:59:36 +00:00
|
|
|
Result.Data[Result.Count] = 0;
|
|
|
|
|
|
|
|
stbsp_vsnprintf((char *)Result.Data, (s32)Result.Count + 1, Format, Arguments);
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string PushFormat(arena *Arena, char *Format, ...)
|
2023-06-21 16:59:36 +00:00
|
|
|
{
|
|
|
|
va_list Arguments;
|
|
|
|
va_start(Arguments, Format);
|
|
|
|
string Result = PushFormatVariadic(Arena, Format, Arguments);
|
|
|
|
va_end(Arguments);
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string PushCString(arena *Arena, char *CString)
|
2023-06-21 16:59:36 +00:00
|
|
|
{
|
|
|
|
string String = MakeString(CString);
|
|
|
|
string Result = PushString(Arena, String);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
//- sixten: Conversion
|
|
|
|
|
2023-06-19 17:12:26 +00:00
|
|
|
static s64 ConvertStringToS64(string String)
|
|
|
|
{
|
|
|
|
s64 Result = 0;
|
|
|
|
b32 IsNegative = false;
|
|
|
|
|
|
|
|
s64 Index = 0;
|
|
|
|
if(String.Data[Index] == '-')
|
|
|
|
{
|
|
|
|
IsNegative = true;
|
|
|
|
++Index;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(;Index < String.Count; ++Index)
|
|
|
|
{
|
|
|
|
u8 Char = String.Data[Index];
|
|
|
|
Assert(IsDigit(Char));
|
|
|
|
Result = Result*10 + (Char-'0');
|
|
|
|
}
|
|
|
|
|
|
|
|
if(IsNegative)
|
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
Result = -Result;;
|
2023-06-19 17:12:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string ConvertS64ToString(arena *Arena, s64 Value)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
|
|
|
b32 IsNegative = (Value < 0);
|
|
|
|
if(IsNegative)
|
|
|
|
{
|
|
|
|
Value = -Value;
|
|
|
|
}
|
|
|
|
|
2023-06-27 14:14:28 +00:00
|
|
|
s64 DigitCount = (s64)Floor(Log(Max(Value, 1LL)) / Log(10)) + 1;
|
2023-06-19 17:12:26 +00:00
|
|
|
|
|
|
|
s64 TotalBufferCount = DigitCount + IsNegative;
|
|
|
|
|
|
|
|
string String = {TotalBufferCount, PushArray(Arena, u8, TotalBufferCount + 1)};
|
|
|
|
String.Data[TotalBufferCount] = 0;
|
|
|
|
|
|
|
|
if(IsNegative)
|
|
|
|
{
|
|
|
|
String.Data[0] = '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < DigitCount;
|
|
|
|
++Index)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-19 17:12:26 +00:00
|
|
|
String.Data[TotalBufferCount - 1 - Index] = '0' + (Value % 10);
|
|
|
|
Value /= 10;
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
|
|
|
|
return(String);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string StringFromCodepoint(arena *Arena, u32 Codepoint)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
char Buffer[5] = {};
|
|
|
|
UTF8FromCodepoint((u8 *)Buffer, Codepoint);
|
2023-06-19 17:12:26 +00:00
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
string Result = PushCString(Arena, Buffer);
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-08-06 10:35:09 +00:00
|
|
|
static r64 DoubleFromString(string String)
|
|
|
|
{
|
|
|
|
temporary_memory Scratch = GetScratch();
|
|
|
|
string NullTerminated = PushString(Scratch.Arena, String);
|
|
|
|
r64 Result = strtod((char *)NullTerminated.Data, 0);
|
|
|
|
ReleaseScratch(Scratch);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-07-19 15:09:41 +00:00
|
|
|
//- sixten: Replacing
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string RemoveAll(arena *Arena, string Text, char ToRemove)
|
2023-07-19 15:09:41 +00:00
|
|
|
{
|
|
|
|
//- sixten: get new count
|
|
|
|
s64 Occurrences = 0;
|
|
|
|
u8 *TextBegin = Text.Data;
|
|
|
|
u8 *TextEnd = TextBegin+Text.Count;
|
|
|
|
for(u8 *Char = TextBegin; Char != TextEnd; Char += 1)
|
|
|
|
{
|
|
|
|
if(*Char == ToRemove)
|
|
|
|
{
|
|
|
|
Occurrences += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//- sixten: copy over all other bytes
|
|
|
|
s64 Count = Text.Count - Occurrences;
|
|
|
|
u8 *Data = PushArrayNoClear(Arena, u8, Count + 1);
|
|
|
|
Data[Count] = 0;
|
|
|
|
|
|
|
|
s64 Index = 0;
|
|
|
|
for(u8 *Char = TextBegin; Char != TextEnd; Char += 1)
|
|
|
|
{
|
|
|
|
if(*Char != ToRemove)
|
|
|
|
{
|
|
|
|
Data[Index] = *Char;
|
|
|
|
Index += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string Result = MakeString(Data, Count);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//- sixten: "C Style" strings
|
|
|
|
|
|
|
|
static s64 StringLength(char *String)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
s64 Result = 0;
|
|
|
|
while(*String++)
|
|
|
|
{
|
|
|
|
++Result;
|
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
|
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-08-22 03:19:51 +00:00
|
|
|
static s64 StringLength16(u16 *String)
|
|
|
|
{
|
|
|
|
s64 Result = 0;
|
|
|
|
while(*String++)
|
|
|
|
{
|
|
|
|
++Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
|
|
|
|
//~ sixten: String list
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static void AppendString(string_list *List, string String, arena *Arena)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
string_node *Node = PushStruct(Arena, string_node);
|
|
|
|
Node->String = String;
|
2023-06-19 17:12:26 +00:00
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
List->TotalCount += String.Count;
|
|
|
|
|
|
|
|
DLLInsertLast(List->First, List->Last, Node);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string JoinStringList(string_list *List, arena *Arena)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
u8 *Buffer = PushArray(Arena, u8, List->TotalCount + 1);
|
|
|
|
Buffer[List->TotalCount] = 0;
|
|
|
|
|
|
|
|
s64 GlobalIndex = 0;
|
|
|
|
|
|
|
|
for(string_node *Node = List->First;
|
|
|
|
Node != 0;
|
|
|
|
Node = Node->Next)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
string String = Node->String;
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < String.Count;
|
|
|
|
++Index)
|
|
|
|
{
|
|
|
|
Buffer[GlobalIndex++] = String.Data[Index];
|
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
}
|
2023-06-21 16:59:36 +00:00
|
|
|
|
|
|
|
string Result = MakeString(Buffer, List->TotalCount);
|
2023-06-19 17:12:26 +00:00
|
|
|
return(Result);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 15:09:41 +00:00
|
|
|
/////////////////////////////////////
|
|
|
|
//~ sixten: String Chunk Functions
|
|
|
|
static string_chunk_list MakeStringChunkList(s64 ChunkSize)
|
|
|
|
{
|
|
|
|
string_chunk_list Result = {};
|
|
|
|
Result.ChunkSize = ChunkSize;
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string JoinStringChunkList(arena *Arena, string_chunk_list *List)
|
2023-07-19 15:09:41 +00:00
|
|
|
{
|
|
|
|
string Result = {};
|
|
|
|
Result.Count = List->TotalCount;
|
|
|
|
Result.Data = PushArrayNoClear(Arena, u8, List->TotalCount + 1);
|
|
|
|
s64 Index = 0;
|
|
|
|
s64 CountRemaining = List->TotalCount;
|
|
|
|
for(string_node *Node = List->First; Node != 0; Node = Node->Next)
|
|
|
|
{
|
|
|
|
string String = Node->String;
|
|
|
|
Copy(Result.Data + Index, String.Data, Min(CountRemaining, List->ChunkSize));
|
|
|
|
CountRemaining -= List->ChunkSize;
|
|
|
|
Index += String.Count;
|
|
|
|
}
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// sixten(TODO): Incomplete, remove maybe?
|
2023-10-29 10:00:34 +00:00
|
|
|
static void ReplaceRange(arena *Arena, string_chunk_list *List, string Text, range1_s64 Range)
|
2023-07-19 15:09:41 +00:00
|
|
|
{
|
|
|
|
s64 NewTotalCount = Max(0ULL, List->TotalCount - DimOfRange(Range)) + Text.Count;
|
|
|
|
|
|
|
|
//- sixten: do we need to allocate more chunks?
|
|
|
|
if(List->ChunkSize*List->ChunkCount < NewTotalCount)
|
|
|
|
{
|
|
|
|
s64 ChunksToAlloc = (NewTotalCount - List->ChunkSize*List->ChunkCount)/List->ChunkSize + 1;
|
|
|
|
for(s64 Index = 0; Index < ChunksToAlloc; Index += 1)
|
|
|
|
{
|
|
|
|
if(DLLIsEmpty(List->FirstFree))
|
|
|
|
{
|
|
|
|
string_node *Node = PushStructNoClear(Arena, string_node);
|
|
|
|
Node->String.Count = 0;
|
|
|
|
Node->String.Data = PushArrayNoClear(Arena, u8, List->ChunkSize);
|
|
|
|
DLLInsertLast(List->First, List->Last, Node);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string_node *Node = List->FirstFree;
|
|
|
|
Node->String.Count = 0;
|
|
|
|
DLLRemove(List->FirstFree, List->LastFree, Node);
|
|
|
|
DLLInsertLast(List->First, List->Last, Node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List->ChunkCount += ChunksToAlloc;
|
|
|
|
}
|
|
|
|
|
|
|
|
s64 CountDelta = NewTotalCount - List->TotalCount;
|
|
|
|
|
|
|
|
// sixten: I cannot be bothered enough to figure out the correct implementation for this. However, if I do this - remember that you can rearrange
|
|
|
|
// the ordering of the linked list, instead of actually copying over the bytes for the majority of this.
|
|
|
|
Assert(AbsoluteValue(CountDelta) < List->ChunkSize);
|
|
|
|
|
|
|
|
//- sixten: find the first and last affected nodes
|
|
|
|
s64 FirstAffectedNodeIndex = Range.Min/List->ChunkSize;
|
|
|
|
s64 LastAffectedNodeIndex = Range.Max/List->ChunkSize;
|
|
|
|
string_node *FirstAffectedNode = List->First;
|
|
|
|
for(s64 WalkIndex = 0; WalkIndex < FirstAffectedNodeIndex; WalkIndex += 1)
|
|
|
|
{
|
|
|
|
FirstAffectedNode = FirstAffectedNode->Next;
|
|
|
|
}
|
|
|
|
string_node *LastAffectedNode = FirstAffectedNode;
|
|
|
|
for(s64 WalkIndex = 0; WalkIndex < LastAffectedNodeIndex-FirstAffectedNodeIndex; WalkIndex += 1)
|
|
|
|
{
|
|
|
|
LastAffectedNode = LastAffectedNode->Next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(CountDelta >= 0)
|
|
|
|
{
|
|
|
|
//- sixten: insertion - make room and the copy the data
|
|
|
|
s64 WriteOffset = Range.Min%List->ChunkSize;
|
|
|
|
for(string_node *Node = List->Last; Node != 0; Node = Node->Prev)
|
|
|
|
{
|
|
|
|
CopyReverse(Node->String.Data+CountDelta+WriteOffset, Node->String.Data+WriteOffset, List->ChunkSize-CountDelta-WriteOffset);
|
|
|
|
|
|
|
|
if(Node == LastAffectedNode)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Copy(Node->String.Data, Node->Prev->String.Data+List->ChunkSize-CountDelta, CountDelta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s64 SourceOffset = 0;
|
|
|
|
for(string_node *Node = FirstAffectedNode; Node != 0; Node = Node->Next)
|
|
|
|
{
|
|
|
|
Copy(Node->String.Data+WriteOffset, Text.Data+SourceOffset, Min(List->ChunkSize-WriteOffset, Text.Count-SourceOffset));
|
|
|
|
SourceOffset += List->ChunkSize;
|
|
|
|
if(Node == LastAffectedNode)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(CountDelta < 0)
|
|
|
|
{
|
|
|
|
//- sixten: deletion
|
|
|
|
}
|
|
|
|
|
|
|
|
List->TotalCount = NewTotalCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-17 17:00:55 +00:00
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
//~ sixten: Unicode
|
2023-06-17 17:00:55 +00:00
|
|
|
|
2023-07-19 15:09:41 +00:00
|
|
|
read_only u8 UTF8Lengths[] =
|
|
|
|
{
|
|
|
|
1, 1, 1, 1, // 000xx
|
|
|
|
1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1,
|
|
|
|
1, 1, 1, 1,
|
|
|
|
0, 0, 0, 0, // 100xx
|
|
|
|
0, 0, 0, 0,
|
|
|
|
2, 2, 2, 2, // 110xx
|
|
|
|
3, 3, // 1110x
|
|
|
|
4, // 11110
|
|
|
|
0, // 11111
|
|
|
|
};
|
|
|
|
|
2023-07-24 13:50:57 +00:00
|
|
|
static string_decode DecodeUTF8Codepoint(u8 *Data, s64 Count)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
string_decode Result = {};
|
|
|
|
u8 FirstByteMask[] = {0, 0x7F, 0x1F, 0x0F, 0x07};
|
|
|
|
u8 FinalShift[] = {0, 18, 12, 6, 0};
|
|
|
|
if(Count > 0)
|
|
|
|
{
|
|
|
|
Result.Codepoint = '#';
|
|
|
|
Result.Size = 1;
|
|
|
|
|
|
|
|
u8 Byte = Data[0];
|
|
|
|
u8 Length = UTF8Lengths[Byte>>3];
|
|
|
|
if(0 < Length && Length <= Count)
|
|
|
|
{
|
|
|
|
u32 Codepoint = (Byte&FirstByteMask[Length])<<18;
|
|
|
|
switch(Length)
|
|
|
|
{
|
|
|
|
case 4: {Codepoint |= ((Data[3] & 0x3F) << 0);} fallthrough;
|
|
|
|
case 3: {Codepoint |= ((Data[2] & 0x3F) << 6);} fallthrough;
|
|
|
|
case 2: {Codepoint |= ((Data[1] & 0x3F) << 12);} fallthrough;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result.Codepoint = Codepoint >> FinalShift[Length];
|
|
|
|
Result.Size = Length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(Result);
|
2023-06-21 16:59:36 +00:00
|
|
|
}
|
2023-06-17 17:00:55 +00:00
|
|
|
|
2023-07-24 13:50:57 +00:00
|
|
|
static u32 EncodeUTF8Codepoint(u8 *Dest, u32 Codepoint)
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
u32 Size = 0;
|
|
|
|
u8 DummyDest[4];
|
|
|
|
Dest = Dest?Dest:DummyDest;
|
2023-08-22 03:19:51 +00:00
|
|
|
if(Codepoint < (1<<7))
|
2023-06-17 17:00:55 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
Dest[0] = Codepoint;
|
|
|
|
Size = 1;
|
|
|
|
}
|
|
|
|
else if (Codepoint < (1 << 11))
|
|
|
|
{
|
|
|
|
Dest[0] = 0xC0|(Codepoint >> 6);
|
|
|
|
Dest[1] = 0x80|(Codepoint & 0x3F);
|
|
|
|
Size = 2;
|
|
|
|
}
|
|
|
|
else if (Codepoint < (1 << 16))
|
|
|
|
{
|
|
|
|
Dest[0] = 0xE0|(Codepoint >> 12);
|
|
|
|
Dest[1] = 0x80|((Codepoint >> 6) & 0x3F);
|
|
|
|
Dest[2] = 0x80|(Codepoint & 0x3F);
|
|
|
|
Size = 3;
|
|
|
|
}
|
|
|
|
else if (Codepoint < (1 << 21))
|
|
|
|
{
|
|
|
|
Dest[0] = 0xF0|(Codepoint >> 18);
|
|
|
|
Dest[1] = 0x80|((Codepoint >> 12) & 0x3F);
|
|
|
|
Dest[2] = 0x80|((Codepoint >> 6) & 0x3F);
|
|
|
|
Dest[3] = 0x80|(Codepoint & 0x3F);
|
|
|
|
Size = 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Dest[0] = '#';
|
|
|
|
Size = 1;
|
|
|
|
}
|
|
|
|
return(Size);
|
|
|
|
}
|
|
|
|
|
2023-08-06 10:35:09 +00:00
|
|
|
static string_decode DecodeUTF16Codepoint(u16 *Data, s64 Count)
|
2023-07-24 13:50:57 +00:00
|
|
|
{
|
|
|
|
string_decode Result = {'#', 1};
|
|
|
|
if(Data[0] < 0xD800 || 0xDFFF < Data[0])
|
|
|
|
{
|
|
|
|
Result.Codepoint = Data[0];
|
|
|
|
Result.Size = 1;
|
|
|
|
}
|
|
|
|
else if(Count >= 2)
|
|
|
|
{
|
|
|
|
if(0xD800 <= Data[0] && Data[0] < 0xDC00 &&
|
|
|
|
0xDC00 <= Data[1] && Data[1] < 0xE000)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
Result.Codepoint = ((Data[0] - 0xD800)<<10)|(Data[1]-0xDC00);
|
|
|
|
Result.Size = 2;
|
2023-06-19 17:12:26 +00:00
|
|
|
}
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
2023-07-24 13:50:57 +00:00
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 EncodeUTF16Codepoint(u16 *Dest, u32 Codepoint)
|
|
|
|
{
|
|
|
|
u32 Size = 0;
|
|
|
|
u16 DummyDest[2];
|
|
|
|
Dest = Dest?Dest:DummyDest;
|
|
|
|
if(Codepoint < 0x10000)
|
|
|
|
{
|
|
|
|
Dest[0] = Codepoint;
|
|
|
|
Size = 1;
|
|
|
|
}
|
2023-06-19 17:12:26 +00:00
|
|
|
else
|
2023-06-18 12:18:34 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
Dest[0] = ((Codepoint - 0x10000) >> 10) + 0xD800;
|
|
|
|
Dest[1] = ((Codepoint - 0x10000) & 0x3FF) + 0xDC00;
|
|
|
|
Size = 2;
|
2023-06-18 12:18:34 +00:00
|
|
|
}
|
2023-07-24 13:50:57 +00:00
|
|
|
return(Size);
|
2023-06-17 17:00:55 +00:00
|
|
|
}
|
|
|
|
|
2023-07-24 13:50:57 +00:00
|
|
|
static s64 UTF8IndexFromOffset(string String, s64 Offset)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-07-24 13:50:57 +00:00
|
|
|
u8 *StringBegin = String.Data;
|
|
|
|
u8 *StringEnd = StringBegin+String.Count;
|
|
|
|
u8 *Byte = StringBegin;
|
2023-08-06 10:35:09 +00:00
|
|
|
for(;Byte < StringEnd && Offset > 0; Offset -= 1)
|
2023-07-24 13:50:57 +00:00
|
|
|
{
|
|
|
|
Byte += DecodeUTF8Codepoint(Byte, StringEnd-Byte).Size;
|
|
|
|
}
|
|
|
|
s64 Result = Byte-StringBegin;
|
2023-06-21 16:59:36 +00:00
|
|
|
return(Result);
|
2023-06-19 17:12:26 +00:00
|
|
|
}
|
|
|
|
|
2023-07-24 13:50:57 +00:00
|
|
|
static s64 UTF8OffsetFromIndex(string String, s64 Index)
|
|
|
|
{
|
|
|
|
s64 Offset = 0;
|
|
|
|
u8 *StringBegin = String.Data;
|
|
|
|
u8 *StringEnd = StringBegin+Min(Index, String.Count);
|
|
|
|
u8 *Byte = StringBegin;
|
|
|
|
for(;Byte < StringEnd;)
|
|
|
|
{
|
|
|
|
Offset += 1;
|
|
|
|
Byte += DecodeUTF8Codepoint(Byte, StringEnd-Byte).Size;
|
|
|
|
}
|
|
|
|
return(Offset);
|
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
s64 Length = 0;
|
|
|
|
if(Codepoint <= 0x7F)
|
2023-06-19 17:12:26 +00:00
|
|
|
{
|
2023-06-21 16:59:36 +00:00
|
|
|
Out[0] = (u8)Codepoint;
|
|
|
|
Length = 1;
|
|
|
|
}
|
|
|
|
else if(Codepoint <= 0x7FF)
|
|
|
|
{
|
|
|
|
Out[0] = (0x3 << 6) | ((Codepoint >> 6) & 0x1F);
|
|
|
|
Out[1] = 0x80 | ( Codepoint & 0x3F);
|
|
|
|
Length = 2;
|
|
|
|
}
|
|
|
|
else if(Codepoint <= 0xFFFF)
|
|
|
|
{
|
|
|
|
Out[0] = (0x7 << 5) | ((Codepoint >> 12) & 0x0F);
|
|
|
|
Out[1] = 0x80 | ((Codepoint >> 6) & 0x3F);
|
|
|
|
Out[2] = 0x80 | ( Codepoint & 0x3F);
|
|
|
|
Length = 3;
|
|
|
|
}
|
|
|
|
else if(Codepoint <= 0x10FFFF)
|
|
|
|
{
|
|
|
|
Out[0] = (0xF << 4) | ((Codepoint >> 12) & 0x07);
|
|
|
|
Out[1] = 0x80 | ((Codepoint >> 12) & 0x3F);
|
|
|
|
Out[2] = 0x80 | ((Codepoint >> 6) & 0x3F);
|
|
|
|
Out[3] = 0x80 | ( Codepoint & 0x3F);
|
|
|
|
Length = 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Out[0] = '?';
|
|
|
|
Length = 1;
|
2023-06-19 17:12:26 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 16:59:36 +00:00
|
|
|
return(Length);
|
2023-07-19 15:09:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string String8FromString16(arena *Arena, string16 String)
|
2023-08-06 10:35:09 +00:00
|
|
|
{
|
|
|
|
s64 AllocGuess = String.Count*3+1;
|
|
|
|
u8 *Memory = PushArray(Arena, u8, AllocGuess);
|
|
|
|
u16 *StringBegin = String.Data;
|
|
|
|
u16 *StringEnd = StringBegin+String.Count;
|
|
|
|
u16 *Source = StringBegin;
|
|
|
|
u8 *Dest = Memory;
|
|
|
|
for(;Source < StringEnd;)
|
|
|
|
{
|
|
|
|
string_decode Decode = DecodeUTF16Codepoint(Source, StringEnd-Source);
|
|
|
|
Dest += EncodeUTF8Codepoint(Dest, Decode.Codepoint);
|
|
|
|
Source += Decode.Size;
|
|
|
|
}
|
|
|
|
*Dest = 0;
|
|
|
|
s64 AllocUsed = Dest-Memory;
|
|
|
|
s64 AllocUnused = AllocGuess-AllocUsed;
|
|
|
|
Assert(AllocUnused >= 0);
|
|
|
|
ArenaPop(Arena, AllocUnused);
|
|
|
|
|
|
|
|
string Result = MakeString(Memory, AllocUsed);
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static string16 String16FromString8(arena *Arena, string String)
|
2023-08-06 10:35:09 +00:00
|
|
|
{
|
|
|
|
s64 AllocGuess = String.Count*2+1;
|
|
|
|
u16 *Memory = PushArray(Arena, u16, AllocGuess);
|
|
|
|
u8 *StringBegin = String.Data;
|
|
|
|
u8 *StringEnd = StringBegin+String.Count;
|
|
|
|
u8 *Source = StringBegin;
|
|
|
|
u16 *Dest = Memory;
|
|
|
|
for(;Source < StringEnd;)
|
|
|
|
{
|
|
|
|
string_decode Decode = DecodeUTF8Codepoint(Source, StringEnd-Source);
|
|
|
|
Dest += EncodeUTF16Codepoint(Dest, Decode.Codepoint);
|
|
|
|
Source += Decode.Size;
|
|
|
|
}
|
|
|
|
*Dest = 0;
|
|
|
|
s64 AllocUsed = Dest-Memory;
|
|
|
|
s64 AllocUnused = AllocGuess-AllocUsed;
|
|
|
|
Assert(AllocUnused >= 0);
|
|
|
|
ArenaPop(Arena, AllocUnused);
|
|
|
|
|
|
|
|
string16 Result = {AllocUsed, Memory};
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
2023-07-19 15:09:41 +00:00
|
|
|
//~ sixten: Text point
|
|
|
|
|
|
|
|
static text_point TextPointFromOffset(string String, s64 Offset)
|
|
|
|
{
|
|
|
|
text_point Point = {1, 1};
|
|
|
|
for(s64 Index = 0;
|
|
|
|
Index < String.Count && Index < Offset;
|
|
|
|
++Index)
|
|
|
|
{
|
|
|
|
if(String.Data[Index] == '\n')
|
|
|
|
{
|
|
|
|
++Point.Line;
|
|
|
|
Point.Column = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(String.Data[Index] != '\r')
|
|
|
|
{
|
|
|
|
++Point.Column;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Point);
|
|
|
|
}
|
|
|
|
|
|
|
|
static s64 OffsetFromTextPoint(string String, text_point Point)
|
|
|
|
{
|
|
|
|
s64 Offset = 0;
|
|
|
|
|
|
|
|
Point.Line -= 1;
|
|
|
|
Point.Column -= 1;
|
|
|
|
|
|
|
|
u8 *StringBegin = String.Data;
|
|
|
|
u8 *StringEnd = StringBegin+String.Count;
|
|
|
|
u8 *Char = StringBegin;
|
|
|
|
//- sixten: find the start of the correct line
|
|
|
|
for(;Char < StringEnd && Point.Line > 0; Char += 1, Offset += 1)
|
|
|
|
{
|
|
|
|
if(*Char == '\n')
|
|
|
|
{
|
|
|
|
Point.Line -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(;Char < StringEnd && Point.Column > 0; Char += 1, Offset += 1)
|
|
|
|
{
|
|
|
|
//- sixten: if a newline has been reached, the initial column was out of bounds
|
|
|
|
if(*Char == '\n')
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
//- sixten: tabs are two-spaces, so we must take that into account
|
|
|
|
#if 0
|
|
|
|
if(*Char == '\t')
|
|
|
|
{
|
|
|
|
Point.Column -= 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
Point.Column -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(Offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
static text_range TextRange(text_point A, text_point B)
|
|
|
|
{
|
|
|
|
text_range Result;
|
|
|
|
if(A.Line > B.Line || (A.Line == B.Line && A.Column > B.Column))
|
|
|
|
{
|
|
|
|
Result = {B, A};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Result = {A, B};
|
|
|
|
}
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////
|
|
|
|
//~ sixten: 1D Interval List & Array Functions
|
2023-10-29 10:00:34 +00:00
|
|
|
static void Range1S64ListPush(arena *Arena, range1_s64_list *List, range1_s64 Range)
|
2023-07-19 15:09:41 +00:00
|
|
|
{
|
|
|
|
range1_s64_node *Node = PushStructNoClear(Arena, range1_s64_node);
|
|
|
|
Node->Range = Range;
|
|
|
|
QueuePush(List->First, List->Last, Node);
|
|
|
|
List->Count += 1;
|
|
|
|
}
|
|
|
|
|
2023-10-29 10:00:34 +00:00
|
|
|
static range1_s64_array Range1S64ArrayFromList(arena *Arena, range1_s64_list *List)
|
2023-07-19 15:09:41 +00:00
|
|
|
{
|
|
|
|
range1_s64_array Result = {};
|
|
|
|
Result.Count = List->Count;
|
|
|
|
Result.Ranges = PushArray(Arena, range1_s64, List->Count);
|
|
|
|
s64 Index = 0;
|
|
|
|
for(range1_s64_node *Node = List->First; Node != 0; Node = Node->Next, Index += 1)
|
|
|
|
{
|
|
|
|
Result.Ranges[Index] = Node->Range;
|
|
|
|
}
|
|
|
|
return(Result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static s64 OffsetFromTextPoint(string String, range1_s64_array Lines, text_point Point)
|
|
|
|
{
|
|
|
|
s64 LineIndex = Clamp(Point.Line, 1, Lines.Count) - 1;
|
|
|
|
range1_s64 Range = Lines.Ranges[LineIndex];
|
|
|
|
s64 ColumnIndex = Clamp(Point.Column, 1, DimOfRange(Range)) - 1;
|
|
|
|
s64 Offset = Range.Min+ColumnIndex;
|
|
|
|
return(Offset);
|
|
|
|
}
|