2203 lines
56 KiB
C
2203 lines
56 KiB
C
#ifndef GS_STRING_H
|
|
////////////////////////////////////////////////////////////////
|
|
// String
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
struct string
|
|
{
|
|
char* Memory;
|
|
s32 Length;
|
|
s32 Max;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Tokenizing
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
struct tokenizer
|
|
{
|
|
char* At;
|
|
char* Memory;
|
|
s32 MemoryLength;
|
|
};
|
|
|
|
|
|
enum token_type
|
|
{
|
|
Token_Error,
|
|
|
|
Token_LeftParen,
|
|
Token_RightParen,
|
|
Token_LeftSquareBracket,
|
|
Token_RightSquareBracket,
|
|
Token_LeftCurlyBracket,
|
|
Token_RightCurlyBracket,
|
|
Token_Semicolon,
|
|
Token_Operator,
|
|
Token_Comma,
|
|
Token_Period,
|
|
Token_PointerReference,
|
|
|
|
Token_PoundDefine,
|
|
Token_PoundUndef,
|
|
Token_PoundInclude,
|
|
Token_PoundIfDef,
|
|
Token_PoundIfNDef,
|
|
Token_PoundIf,
|
|
Token_PoundElif,
|
|
Token_PoundElse,
|
|
Token_PoundEndif,
|
|
Token_PoundError,
|
|
Token_PoundPragma,
|
|
|
|
Token_Number,
|
|
Token_Char,
|
|
Token_String,
|
|
Token_Identifier,
|
|
|
|
Token_Comment,
|
|
Token_MultilineComment,
|
|
|
|
Token_Unknown,
|
|
Token_EndOfStream,
|
|
};
|
|
|
|
char* TokenNames[] = {
|
|
"Token_Error ",
|
|
"Token_LeftParen ",
|
|
"Token_RightParen ",
|
|
"Token_LeftSquareBracket ",
|
|
"Token_RightSquareBracket ",
|
|
"Token_LeftCurlyBracket ",
|
|
"Token_RightCurlyBracket ",
|
|
"Token_Semicolon ",
|
|
"Token_Operator ",
|
|
"Token_Comma ",
|
|
"Token_Period ",
|
|
"Token_PointerReference ",
|
|
"Token_PoundDefine ",
|
|
"Token_PoundUndef ",
|
|
"Token_PoundInclude ",
|
|
"Token_PoundIfDef ",
|
|
"Token_PoundIfNDef ",
|
|
"Token_PoundIf ",
|
|
"Token_PoundElif ",
|
|
"Token_PoundElse ",
|
|
"Token_PoundEndif ",
|
|
"Token_PoundError ",
|
|
"Token_PoundPragma ",
|
|
"Token_Number ",
|
|
"Token_Char ",
|
|
"Token_String ",
|
|
"Token_Identifier ",
|
|
"Token_Comment ",
|
|
"Token_MultilineComment ",
|
|
"Token_Unknown ",
|
|
"Token_EndOfStream ",
|
|
};
|
|
|
|
struct token
|
|
{
|
|
token_type Type;
|
|
string Text;
|
|
token* Next;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Memory
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
struct slot_header
|
|
{
|
|
slot_header* Next;
|
|
s32 Size;
|
|
};
|
|
|
|
struct slot_arena
|
|
{
|
|
u8* Memory;
|
|
s32 SlotSize;
|
|
s32 SlotCount;
|
|
slot_header* FreeList;
|
|
};
|
|
|
|
struct contiguous_slot_count_result
|
|
{
|
|
s32 Count;
|
|
slot_header* LastContiguousSlot;
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Function Declarations
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Utility
|
|
#if !defined GS_LANGUAGE_H
|
|
|
|
static void GSZeroMemory (u8* Memory, s32 Size);
|
|
static s32 GSMin (s32 A, s32 B);
|
|
static s32 GSAbs (s32 A);
|
|
static float GSAbsF (float A);
|
|
static float GSPowF (float N, s32 Power);
|
|
|
|
#endif
|
|
|
|
// Setup
|
|
|
|
#ifdef GS_MEMORY_H
|
|
#define PushString(str, arena, size) (str)->Memory = PushArray(arena, char, size); (str)->Length = 0; (str)->Max = size;
|
|
#endif
|
|
|
|
static void InitializeEmptyString (string* String, char* Data, s32 DataSize);
|
|
static void InitializeString(string* String, char* Data, s32 Used, s32 Max);
|
|
static string InitializeEmptyString (char* Data, s32 DataSize);
|
|
static string InitializeString (char* Data, s32 Used, s32 Max);
|
|
static void ClearString (string* String);
|
|
|
|
// Character Values
|
|
static bool IsSlash (char C);
|
|
static bool IsNewline (char C);
|
|
static bool IsWhitespace (char C);
|
|
static bool IsAlpha (char C);
|
|
static bool IsUpper (char C);
|
|
static bool IsLower (char C);
|
|
static char ToUpper (char C);
|
|
static char ToLower (char C);
|
|
static bool IsNumeric (char C);
|
|
static bool IsNumericExtended (char C);
|
|
static bool IsAlphaNumeric (char C);
|
|
static bool IsOperator (char C);
|
|
static bool CharsEqualCaseInsensitive(char A, char B);
|
|
|
|
// Tokenizing
|
|
static b32 AtValidPosition(tokenizer Tokenizer);
|
|
static b32 AtValidToken(tokenizer Tokenizer);
|
|
static char* EatToNewLine(char* C);
|
|
static void EatToNewLine(tokenizer* T);
|
|
static char* EatWhitespace(char* C);
|
|
static void EatWhitespace(tokenizer* T);
|
|
static char* EatToWhitespace(char* C);
|
|
static void EatToWhitespace(tokenizer* T);
|
|
static char* EatToCharacter(char* C, char Char);
|
|
static void EatToCharacter(tokenizer* T, char Char);
|
|
static char* EatPastCharacter(char* C, char Char);
|
|
static void EatPastCharacter(tokenizer* T, char Char);
|
|
static char* EatNumber(char* C);
|
|
static void EatNumber(tokenizer* T);
|
|
|
|
// Char/Char Array
|
|
static u32 CharToUInt (char C);
|
|
static s32 CharArrayLength (char* CharArray);
|
|
static bool CharArraysEqual (char* A, s32 ALength, char* B, s32 BLength);
|
|
static bool CharArraysEqualUnsafe (char* A, char* B);
|
|
static void ReverseCharArray (char* Array, s32 Length);
|
|
#define FirstIndexOfCharInCharArray(array, find) IndexOfChar(array, 0, find)
|
|
static s32 IndexOfChar (char* Array, s32 Start, char Find);
|
|
#define FastLastIndexOfCharInCharArray(array, len, find) FastReverseIndexOfChar(array, len, 0, find)
|
|
static s32 FastReverseIndexOfChar (char* Array, s32 Length, s32 OffsetFromEnd, char Find);
|
|
#define LastIndexOfCharInCharArray(array, find) ReverseIndexOfChar(array, 0, find)
|
|
static s32 ReverseIndexOfChar (char* Array, s32 OffsetFromEnd, char Find);
|
|
static b32 CharArrayContains(char* Array, char* CheckFor);
|
|
static b32 CharArrayContainsSafe(char* Array, s32 ArrayLength, char* CheckFor, s32 CheckForLength);
|
|
|
|
// String
|
|
|
|
#define MakeStringBuffer(name, size) \
|
|
char name##Backbuffer[(size)]; \
|
|
string name = MakeString(name##Backbuffer, size);
|
|
|
|
static string MakeString (char* Array, s32 Length, s32 Max);
|
|
static string MakeString (char* Array, s32 Length);
|
|
static string MakeString (char* Array);
|
|
static string MakeStringLiteral(char* Data);
|
|
|
|
static bool StringsEqual (string A, string B);
|
|
static bool StringEqualsCharArray (string String, char* CharArray);
|
|
static bool StringEqualsCharArray (string String, char* CharArray, s32 CharArrayLength);
|
|
static s32 FindFirstChar (string String, char C);
|
|
|
|
static void SetStringToChar (string* Dest, char C, s32 Count);
|
|
static void SetStringToCharArray (string* Dest, char* Source);
|
|
|
|
static void ConcatString (string Source, string* Dest);
|
|
static void ConcatString (string Source, s32 Length, string* Dest);
|
|
static void ConcatCharToString(string* Dest, char C);
|
|
static void ConcatCharArrayToString (char* Source, string* Dest);
|
|
static void ConcatCharArrayToString (char* Source, s32 SourceLength, string* Dest);
|
|
|
|
static void CopyStringTo (string Source, string* Dest);
|
|
static s32 CopyStringToCharArray (string Source, char* Dest, s32 DestLength);
|
|
static void CopyCharArrayToString (char* Src, string* Dest);
|
|
static void CopyCharArrayToString (char* Src, s32 SrcLength, string* Dest);
|
|
static s32 CopyCharArray (char* Source, char* Dest, s32 DestLength);
|
|
static s32 CopyCharArrayAt (char* Source, char* Dest, s32 DestLength, s32 Offset);
|
|
|
|
static void InsertChar (string* String, char Char, s32 Index);
|
|
static void InsertStringAt (string* Dest, string Source, s32 At);
|
|
static void RemoveCharAt (string* String, s32 Index);
|
|
|
|
static s32 IndexOfChar(string String, char C);
|
|
static s32 LastIndexOfChar(string String, char C);
|
|
static string Substring (string* String, s32 Start, s32 End);
|
|
static string Substring (string* String, s32 Start);
|
|
|
|
static b32 StringContainsCharArray(string SearchIn, char* SearchFor, s32 SearchForLength);
|
|
static b32 StringContainsString(string SearchIn, string SearchFor);
|
|
static b32 StringContainsCharArrayCaseInsensitive(string SearchIn, char* SearchFor, s32 SearchForLength);
|
|
static b32 StringContainsStringCaseInsensitive(string SearchIn, string SearchFor);
|
|
|
|
static void NullTerminate (string* String);
|
|
|
|
|
|
// Parsing
|
|
enum parse_type
|
|
{
|
|
ParseType_UnsignedInt,
|
|
ParseType_SignedInt,
|
|
ParseType_Float,
|
|
};
|
|
|
|
struct parse_result
|
|
{
|
|
parse_type Type;
|
|
char* OnePastLast;
|
|
union
|
|
{
|
|
u32 UnsignedIntValue;
|
|
s32 SignedIntValue;
|
|
r32 FloatValue;
|
|
};
|
|
};
|
|
|
|
enum format_flags
|
|
{
|
|
FormatFlags_LeftJustify = 0x1,
|
|
FormatFlags_ForceSign = 0x2,
|
|
FormatFlags_ForceSpaceInsteadOfSign = 0x4,
|
|
FormatFlags_ForceDecimalOrPrependOx = 0x8,
|
|
FormatFlags_PadWithZeroesInsteadOfSpaces = 0x16,
|
|
};
|
|
|
|
static parse_result ParseUnsignedInt (char* String, s32 Length);
|
|
static parse_result ParseSignedInt (char* String, s32 Length);
|
|
static parse_result ParseFloat (char* String, s32 Length);
|
|
|
|
// PrintF
|
|
static void PrintFArgList(char* Dest, s32 DestMax, char* Format, va_list Args);
|
|
static void PrintF(string* String, char* Format, ...);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Memory Function Declarations
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static s32 CalculateSlotCountFromSize (s32 RequestedSize, s32 SlotSize);
|
|
static bool SlotsAreContiguous (slot_header* First, slot_header* Second);
|
|
static contiguous_slot_count_result CountContiguousSlots (slot_header* First);
|
|
static slot_header* GetSlotAtOffset(slot_header* First, s32 Offset);
|
|
static slot_header* InsertSlotIntoList (slot_header* NewSlot, slot_header* ListStart);
|
|
static void AllocStringFromStringArena (string* String, s32 Size, slot_arena* Storage);
|
|
static string AllocStringFromStringArena (s32 Size, slot_arena* Storage);
|
|
static void FreeToStringArena (string* String, slot_arena* Storage);
|
|
static void ReallocFromStringArena (string* String, s32 NewSize, slot_arena* Storage);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Utility Functions
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#if !defined GS_LANGUAGE_H
|
|
|
|
static void
|
|
GSZeroMemory (u8* Memory, s32 Size)
|
|
{
|
|
for (int i = 0; i < Size; i++) { Memory[i] = 0; }
|
|
}
|
|
|
|
static s32
|
|
GSMin (s32 A, s32 B)
|
|
{
|
|
return (A < B ? A : B);
|
|
}
|
|
|
|
static s32
|
|
GSAbs (s32 A)
|
|
{
|
|
return (A < 0 ? -A : A);
|
|
}
|
|
|
|
static float
|
|
GSAbs (float A)
|
|
{
|
|
return (A < 0 ? -A : A);
|
|
}
|
|
|
|
static float
|
|
GSPow (float N, s32 Power)
|
|
{
|
|
float Result = N;
|
|
for(s32 i = 1; i < Power; i++) { Result *= N; }
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Init and Clear
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
InitializeEmptyString (string* String, char* Data, s32 DataSize)
|
|
{
|
|
String->Memory = Data;
|
|
String->Max = DataSize;
|
|
String->Length = 0;
|
|
}
|
|
|
|
static void
|
|
InitializeString(string* String, char* Data, s32 Used, s32 Max)
|
|
{
|
|
String->Memory = Data;
|
|
String->Max = Max;
|
|
String->Length = Used;
|
|
}
|
|
|
|
static string
|
|
InitializeEmptyString (char* Data, s32 DataSize)
|
|
{
|
|
string Result = {};
|
|
Result.Memory = Data;
|
|
Result.Max = DataSize;
|
|
Result.Length = 0;
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
InitializeString (char* Data, s32 Used, s32 Max)
|
|
{
|
|
string Result = {};
|
|
Result.Memory = Data;
|
|
Result.Max = Max;
|
|
Result.Length = Used;
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
ClearString (string* String)
|
|
{
|
|
String->Memory = 0;
|
|
String->Max = 0;
|
|
String->Length = 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Char Value Types
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static bool IsSlash (char C) { return ((C == '\\') || (C == '/')); }
|
|
static bool IsNewline (char C) { return (C == '\n') || (C == '\r'); }
|
|
static bool IsWhitespace (char C) { return (C == ' ') || (C == '\t') || IsNewline(C); }
|
|
static bool IsAlpha (char C)
|
|
{
|
|
// TODO(Peter): support UTF8 chars
|
|
return ((C >= 'A') && (C <= 'Z')) || ((C >= 'a') && (C <= 'z')) || (C == '_');
|
|
}
|
|
static bool IsUpper (char C)
|
|
{
|
|
return ((C >= 'A') && (C <= 'Z'));
|
|
}
|
|
static bool IsLower (char C)
|
|
{
|
|
return ((C >= 'a') && (C <= 'z'));
|
|
}
|
|
static bool IsNumeric (char C)
|
|
{
|
|
return (C >= '0') && (C <= '9');
|
|
}
|
|
static bool IsNumericExtended (char C)
|
|
{
|
|
return (IsNumeric(C) || (C == 'x') || (C == 'f') || (C == '.'));
|
|
}
|
|
static bool IsAlphaNumeric (char C)
|
|
{
|
|
return IsAlpha(C) || IsNumeric(C);
|
|
}
|
|
static bool IsOperator (char C)
|
|
{
|
|
return ((C == '+') ||
|
|
(C == '-') ||
|
|
(C == '*') ||
|
|
(C == '/') ||
|
|
(C == '=') ||
|
|
(C == '%') ||
|
|
(C == '<') ||
|
|
(C == '>'));
|
|
}
|
|
static char ToUpper (char A)
|
|
{
|
|
char Result = A;
|
|
if (IsLower(A))
|
|
{
|
|
Result += 'A' - 'a';
|
|
}
|
|
return Result;
|
|
}
|
|
static char ToLower (char A)
|
|
{
|
|
char Result = A;
|
|
if (IsUpper(A))
|
|
{
|
|
Result -= 'A' - 'a';
|
|
}
|
|
return Result;
|
|
}
|
|
static bool CharsEqualCaseInsensitive (char A, char B)
|
|
{
|
|
b32 Result = (ToLower(A) == ToLower(B));
|
|
return Result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Tokenizing
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static b32
|
|
AtValidPosition (tokenizer Tokenizer)
|
|
{
|
|
b32 Result = (Tokenizer.At - Tokenizer.Memory) <= Tokenizer.MemoryLength;
|
|
return Result;
|
|
}
|
|
|
|
static b32
|
|
AtValidToken(tokenizer Tokenizer)
|
|
{
|
|
b32 Result = *Tokenizer.At && Tokenizer.At < (Tokenizer.Memory + Tokenizer.MemoryLength);
|
|
return Result;
|
|
}
|
|
|
|
static char*
|
|
EatToNewLine(char* C)
|
|
{
|
|
char* Result = C;
|
|
while (*Result && !IsNewline(*Result)) { Result++; }
|
|
if (*Result) { Result++; } // NOTE(Peter): eat past the newline character
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatToNewLine(tokenizer* T)
|
|
{
|
|
while (*T->At && !IsNewline(*T->At)) { T->At++; }
|
|
if (*T->At) { T->At++; } // NOTE(Peter): eat past the newline character
|
|
}
|
|
|
|
static char*
|
|
EatWhitespace(char* C)
|
|
{
|
|
char* Result = C;
|
|
while (*Result && IsWhitespace(*Result)) { Result++; }
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatWhitespace(tokenizer* T)
|
|
{
|
|
while (*T->At && IsWhitespace(*T->At)) { T->At++; }
|
|
}
|
|
|
|
static char*
|
|
EatToWhitespace(char* C)
|
|
{
|
|
char* Result = C;
|
|
while (*Result && !IsWhitespace(*Result)) { Result++; }
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatToWhitespace(tokenizer* T)
|
|
{
|
|
while (*T->At && !IsWhitespace(*T->At)) { T->At++; }
|
|
}
|
|
|
|
static char*
|
|
EatToCharacter(char* C, char Char)
|
|
{
|
|
char* Result = C;
|
|
while (*Result && *Result != Char) { Result++; }
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatToCharacter(tokenizer* T, char Char)
|
|
{
|
|
while (*T->At && *T->At != Char) { T->At++; }
|
|
}
|
|
|
|
static char*
|
|
EatPastCharacter(char* C, char Char)
|
|
{
|
|
char* Result = EatToCharacter(C, Char);
|
|
if (*Result && *Result == Char) { Result++; }
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatPastCharacter(tokenizer* T, char Char)
|
|
{
|
|
EatToCharacter(T, Char);
|
|
if (*T->At && *T->At == Char) { T->At++; }
|
|
}
|
|
|
|
static char*
|
|
EatNumber(char* C)
|
|
{
|
|
char* Result = C;
|
|
while (*Result && IsNumericExtended(*Result)) { Result++; }
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
EatNumber(tokenizer* T)
|
|
{
|
|
while (*T->At && IsNumericExtended(*T->At)) { T->At++; }
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Basic Char Operations
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static u32 CharToUInt (char C) {
|
|
u32 Result = (C - '0');
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
CharArrayLength (char* Array)
|
|
{
|
|
char* C = Array;
|
|
s32 Result = 0;
|
|
while (*C)
|
|
{
|
|
*C++;
|
|
Result++;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
NullTerminatedCharArrayLength (char* CharArray)
|
|
{
|
|
char* Iter = CharArray;
|
|
while (*Iter)
|
|
{
|
|
*Iter++;
|
|
}
|
|
return (Iter - CharArray);
|
|
}
|
|
|
|
static bool
|
|
CharArraysEqual (char* A, s32 ALength, char* B, s32 BLength)
|
|
{
|
|
bool Result = false;
|
|
if (ALength == BLength)
|
|
{
|
|
Result = true;
|
|
char* AIter = A;
|
|
char* BIter = B;
|
|
for (s32 i = 0; i < ALength; i++)
|
|
{
|
|
if(*AIter++ != *BIter++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static bool
|
|
CharArraysEqualUnsafe (char* A, char* B)
|
|
{
|
|
bool Result = true;
|
|
|
|
char* AIter = A;
|
|
char* BIter = B;
|
|
while(*AIter && *BIter)
|
|
{
|
|
if(*AIter++ != *BIter++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if((*AIter && !*BIter) || (!*AIter && *BIter))
|
|
{
|
|
Result = false;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static bool
|
|
CharArraysEqualUpToLength (char* A, char* B, s32 Length)
|
|
{
|
|
bool Result = true;
|
|
|
|
char* AIter = A;
|
|
char* BIter = B;
|
|
for (s32 i = 0; i < Length; i++)
|
|
{
|
|
if(*AIter++ != *BIter++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
ReverseCharArray (char* Array, s32 Length)
|
|
{
|
|
char* ForwardIter = Array;
|
|
char* BackwardIter = Array + Length - 1;
|
|
for (s32 i = 0; i < (Length / 2); i++)
|
|
{
|
|
char F = *ForwardIter;
|
|
char B = *BackwardIter;
|
|
*ForwardIter++ = B;
|
|
*BackwardIter-- = F;
|
|
}
|
|
}
|
|
|
|
static s32
|
|
IndexOfChar (char* Array, s32 After, char Find)
|
|
{
|
|
s32 Result = -1;
|
|
|
|
s32 Counter = After;
|
|
char* Iter = Array + After;
|
|
while (*Iter)
|
|
{
|
|
if (*Iter == Find)
|
|
{
|
|
Result = Counter;
|
|
break;
|
|
}
|
|
Counter++;
|
|
*Iter++;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
FastReverseIndexOfChar (char* Array, s32 Length, s32 OffsetFromEnd, char Find)
|
|
{
|
|
s32 Result = -1;
|
|
|
|
s32 Counter = Length - OffsetFromEnd;
|
|
char* Iter = Array + Length - OffsetFromEnd;
|
|
for (int i = 0; i < (Length - OffsetFromEnd); i++)
|
|
{
|
|
if (*Iter == Find)
|
|
{
|
|
Result = Counter;
|
|
break;
|
|
}
|
|
|
|
*Iter--;
|
|
Counter--;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
ReverseIndexOfChar (char* Array, s32 OffsetFromEnd, char Find)
|
|
{
|
|
s32 StringLength = NullTerminatedCharArrayLength(Array);
|
|
return FastReverseIndexOfChar(Array, StringLength, OffsetFromEnd, Find);
|
|
}
|
|
|
|
static b32
|
|
CharArrayContains(char* Array, char* CheckFor)
|
|
{
|
|
b32 Result = false;
|
|
|
|
char* Src = Array;
|
|
while (*Src)
|
|
{
|
|
if (*Src == *CheckFor)
|
|
{
|
|
char* A = CheckFor;
|
|
char* B = Src;
|
|
while (*B && *A && *A == *B)
|
|
{
|
|
*B++; *A++;
|
|
}
|
|
|
|
if (*A == 0)
|
|
{
|
|
Result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Src++;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static b32
|
|
CharArrayContainsSafe(char* Array, s32 ArrayLength, char* CheckFor, s32 CheckForLength)
|
|
{
|
|
b32 Result = false;
|
|
|
|
if (ArrayLength >= CheckForLength)
|
|
{
|
|
char* Src = Array;
|
|
for (s32 s = 0; s < ArrayLength; s++)
|
|
{
|
|
if (*Src == *CheckFor && (s + CheckForLength <= ArrayLength))
|
|
{
|
|
char* A = CheckFor;
|
|
char* B = Src;
|
|
for (s32 d = 0; d < CheckForLength; d++)
|
|
{
|
|
if (*B != *A) { break; }
|
|
*B++; *A++;
|
|
}
|
|
|
|
if (*A == 0)
|
|
{
|
|
Result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Src++;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Basic String Operations
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static bool
|
|
StringsEqual (string A, string B)
|
|
{
|
|
bool Result = false;
|
|
|
|
if (A.Length == B.Length)
|
|
{
|
|
Result = true;
|
|
char* AIter = A.Memory;
|
|
char* BIter = B.Memory;
|
|
for (s32 i = 0; i < A.Length; i++)
|
|
{
|
|
if (*AIter++ != *BIter++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
MakeString (char* Array, s32 Length, s32 Max)
|
|
{
|
|
string Result = {};
|
|
Result.Memory = Array;
|
|
Result.Length = Length;
|
|
Result.Max = Max;
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
MakeString (char* Array, s32 Length)
|
|
{
|
|
string Result = {};
|
|
Result.Memory = Array;
|
|
Result.Length = Length;
|
|
Result.Max = Length;
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
MakeString (char* Array)
|
|
{
|
|
s32 Length = CharArrayLength (Array);
|
|
return MakeString(Array, Length);
|
|
}
|
|
|
|
static string
|
|
MakeStringLiteral (char* String)
|
|
{
|
|
string Result = {};
|
|
Result.Memory = String;
|
|
Result.Max = CharArrayLength(String);
|
|
Result.Length = Result.Max;
|
|
return Result;
|
|
}
|
|
|
|
static bool
|
|
StringEqualsCharArray (string String, char* CharArray)
|
|
{
|
|
bool Result = true;
|
|
|
|
char* S = String.Memory;
|
|
char* C = CharArray;
|
|
|
|
s32 Count = 0;
|
|
while (*C && Count < String.Length)
|
|
{
|
|
if (*C++ != *S++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
Count++;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static bool
|
|
StringEqualsCharArray (string String, char* CharArray, s32 CharArrayLength)
|
|
{
|
|
bool Result = false;
|
|
|
|
if (CharArrayLength == String.Length)
|
|
{
|
|
Result = true;
|
|
|
|
char* S = String.Memory;
|
|
char* C = CharArray;
|
|
for (s32 i = 0; i < String.Length; i++)
|
|
{
|
|
if (*C++ != *S++)
|
|
{
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
FindFirstChar (string String, char C)
|
|
{
|
|
s32 Result = -1;
|
|
|
|
char* Iter = String.Memory;
|
|
for (int i = 0; i < String.Length; i++)
|
|
{
|
|
if (*Iter++ == C)
|
|
{
|
|
Result = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
SetStringToChar (string* Dest, char C, s32 Count)
|
|
{
|
|
Assert(Count <= Dest->Max);
|
|
|
|
char* Iter = Dest->Memory;
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
*Iter++ = C;
|
|
}
|
|
Dest->Length = Count;
|
|
}
|
|
|
|
static void
|
|
SetStringToCharArray (string* Dest, char* Source)
|
|
{
|
|
Dest->Length = 0;
|
|
|
|
char* Src = Source;
|
|
char* Dst = Dest->Memory;
|
|
while (*Src && Dest->Length < Dest->Max)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Dest->Length++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ConcatString (string Source, string* Dest)
|
|
{
|
|
Assert((Dest->Length + Source.Length) <= Dest->Max);
|
|
|
|
char* Dst = Dest->Memory + Dest->Length;
|
|
char* Src = Source.Memory;
|
|
for (s32 i = 0; i < Source.Length; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Dest->Length++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ConcatString (string Source, s32 Length, string* Dest)
|
|
{
|
|
Assert(Length < Source.Length);
|
|
Assert((Dest->Length + Length) <= Dest->Max);
|
|
|
|
char* Dst = Dest->Memory + Dest->Length;
|
|
char* Src = Source.Memory;
|
|
for (s32 i = 0; i < Length; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Dest->Length++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ConcatCharToString (string* Dest, char C)
|
|
{
|
|
Assert(Dest->Length + 1 <= Dest->Max);
|
|
|
|
char* Dst = Dest->Memory + Dest->Length;
|
|
*Dst = C;
|
|
Dest->Length++;
|
|
}
|
|
|
|
static void
|
|
ConcatCharArrayToString (char* Source, string* Dest)
|
|
{
|
|
Assert(CharArrayLength(Source) + Dest->Length <= Dest->Max);
|
|
|
|
char* Dst = Dest->Memory + Dest->Length;
|
|
char* Src = Source;
|
|
while (Dest->Length < Dest->Max &&
|
|
*Src)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Dest->Length++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ConcatCharArrayToString (char* Source, s32 SourceLength, string* Dest)
|
|
{
|
|
Assert(SourceLength + Dest->Length <= Dest->Max);
|
|
|
|
char* Dst = Dest->Memory + Dest->Length;
|
|
char* Src = Source;
|
|
for (int i = 0; i < SourceLength && Dest->Length < Dest->Max; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Dest->Length++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
CopyStringTo (string Source, string* Dest)
|
|
{
|
|
char* Src = Source.Memory;
|
|
char* Dst = Dest->Memory;
|
|
s32 CopyLength = GSMin(Source.Length, Dest->Max);
|
|
for (int i = 0; i < CopyLength; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
}
|
|
Dest->Length = Source.Length;
|
|
}
|
|
|
|
static s32
|
|
CopyStringToCharArray (string Source, char* Dest, s32 DestLength)
|
|
{
|
|
char* Src = Source.Memory;
|
|
char* Dst = Dest;
|
|
s32 CopyLength = GSMin(Source.Length, DestLength);
|
|
for (int i = 0; i < CopyLength; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
}
|
|
return CopyLength;
|
|
}
|
|
|
|
static void
|
|
CopyCharArrayToString (char* Source, string* Dest)
|
|
{
|
|
char* Src = Source;
|
|
char* Dst = Dest->Memory;
|
|
s32 Copied = 0;
|
|
while (*Src && Copied < Dest->Max)
|
|
{
|
|
*Dst++ = *Src++;
|
|
Copied++;
|
|
}
|
|
Dest->Length = Copied;
|
|
}
|
|
|
|
static void
|
|
CopyCharArrayToString (char* Source, s32 SourceLength, string* Dest)
|
|
{
|
|
Assert(SourceLength <= Dest->Max);
|
|
|
|
char* Src = Source;
|
|
char* Dst = Dest->Memory;
|
|
for (s32 i = 0; i < SourceLength; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
}
|
|
Dest->Length = SourceLength;
|
|
}
|
|
|
|
static s32
|
|
CopyCharArray (char* Source, char* Dest, s32 DestLength)
|
|
{
|
|
char* Src = Source;
|
|
char* Dst = Dest;
|
|
s32 i = 0;
|
|
while (*Src && i < DestLength)
|
|
{
|
|
*Dst++ = *Src++;
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static s32
|
|
CopyCharArrayAt (char* Source, char* Dest, s32 DestLength, s32 Offset)
|
|
{
|
|
Assert(Offset < DestLength);
|
|
|
|
char* Src = Source;
|
|
char* Dst = Dest + Offset;
|
|
s32 i = Offset;
|
|
while (*Src && i < DestLength)
|
|
{
|
|
*Dst++ = *Src++;
|
|
i++;
|
|
}
|
|
return i - Offset;
|
|
}
|
|
|
|
static void
|
|
InsertChar (string* String, char Char, s32 Index)
|
|
{
|
|
Assert(Index >= 0 && Index < String->Max);
|
|
Assert(String->Length < String->Max);
|
|
|
|
char* Src = String->Memory + String->Length - 1;
|
|
char* Dst = Src + 1;
|
|
for (int i = String->Length - 1; i >= Index; i--)
|
|
{
|
|
*Dst-- = *Src--;
|
|
}
|
|
|
|
*(String->Memory + Index) = Char;
|
|
String->Length++;
|
|
}
|
|
|
|
static void
|
|
RemoveCharAt (string* String, s32 Index)
|
|
{
|
|
Assert(Index >= 0 && Index < String->Max);
|
|
|
|
char* Dst = String->Memory + Index;
|
|
char* Src = Dst + 1;
|
|
for (int i = Index; i < String->Length; i++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
}
|
|
*Dst = 0;
|
|
String->Length--;
|
|
}
|
|
|
|
static s32
|
|
IndexOfChar(string String, char C)
|
|
{
|
|
s32 Result = -1;
|
|
char* At = String.Memory;
|
|
for (s32 i = 0; i < String.Length; i++)
|
|
{
|
|
if (*At == C)
|
|
{
|
|
Result = i;
|
|
break;
|
|
}
|
|
At++;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static s32
|
|
LastIndexOfChar(string String, char C)
|
|
{
|
|
s32 Result = -1;
|
|
char* At = String.Memory + String.Length - 1;
|
|
for (s32 i = 0; i < String.Length; i++)
|
|
{
|
|
if (*At == C)
|
|
{
|
|
Result = String.Length - i;
|
|
break;
|
|
}
|
|
At--;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
Substring (string String, s32 Start, s32 End)
|
|
{
|
|
Assert(Start >= 0 && End > Start && End <= String.Length);
|
|
|
|
string Result = {};
|
|
Result.Memory = String.Memory + Start;
|
|
Result.Length = End - Start;
|
|
return Result;
|
|
}
|
|
|
|
static string
|
|
Substring (string String, s32 Start)
|
|
{
|
|
Assert(Start >= 0 && Start < String.Length);
|
|
|
|
string Result = {};
|
|
Result.Memory = String.Memory + Start;
|
|
Result.Length = String.Length - Start;
|
|
return Result;
|
|
}
|
|
|
|
static b32
|
|
StringContainsCharArray(string SearchIn, char* SearchFor, s32 SearchForLength)
|
|
{
|
|
b32 Result = false;
|
|
|
|
char* SearchInAt = SearchIn.Memory;
|
|
for (s32 i = 0; i < (SearchIn.Length - SearchForLength) + 1; i++)
|
|
{
|
|
char* InAt = SearchInAt;
|
|
char* ForAt = SearchFor;
|
|
s32 LengthMatch = 0;
|
|
while (*InAt == *ForAt)
|
|
{
|
|
InAt++;
|
|
ForAt++;
|
|
LengthMatch++;
|
|
}
|
|
if (LengthMatch == SearchForLength)
|
|
{
|
|
Result = true;
|
|
break;
|
|
}
|
|
SearchInAt++;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static b32
|
|
StringContainsString(string SearchIn, string SearchFor)
|
|
{
|
|
return StringContainsCharArray(SearchIn, SearchFor.Memory, SearchFor.Length);
|
|
}
|
|
|
|
static b32
|
|
StringContainsCharArrayCaseInsensitive(string SearchIn, char* SearchFor, s32 SearchForLength)
|
|
{
|
|
b32 Result = false;
|
|
|
|
char* SearchInAt = SearchIn.Memory;
|
|
for (s32 i = 0; i < (SearchIn.Length - SearchForLength) + 1; i++)
|
|
{
|
|
char* InAt = SearchInAt;
|
|
char* ForAt = SearchFor;
|
|
s32 LengthMatch = 0;
|
|
while (CharsEqualCaseInsensitive(*InAt, *ForAt))
|
|
{
|
|
InAt++;
|
|
ForAt++;
|
|
LengthMatch++;
|
|
}
|
|
if (LengthMatch == SearchForLength)
|
|
{
|
|
Result = true;
|
|
break;
|
|
}
|
|
SearchInAt++;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static b32
|
|
StringContainsStringCaseInsensitive(string SearchIn, string SearchFor)
|
|
{
|
|
return StringContainsCharArrayCaseInsensitive(SearchIn, SearchFor.Memory, SearchFor.Length);
|
|
}
|
|
|
|
static void
|
|
NullTerminate (string* String)
|
|
{
|
|
Assert(String->Length + 1 <= String->Max);
|
|
*(String->Memory + String->Length) = 0;
|
|
String->Length++;
|
|
}
|
|
|
|
static void
|
|
InsertStringAt (string* Dest, string Source, s32 At)
|
|
{
|
|
Assert(At + Source.Length < Dest->Max);
|
|
Assert(At < Dest->Length);
|
|
|
|
char* Src = Dest->Memory + Dest->Length;
|
|
char* Dst = Dest->Memory + Source.Length + Dest->Length;
|
|
for (s32 i = Dest->Length - 1; i >= At; i--)
|
|
{
|
|
*--Dst = *--Src;
|
|
}
|
|
|
|
Src = Source.Memory;
|
|
Dst = Dest->Memory + At;
|
|
for (s32 j = 0; j < Source.Length; j++)
|
|
{
|
|
*Dst++ = *Src++;
|
|
}
|
|
|
|
Dest->Length += Source.Length;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Parsing
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static parse_result
|
|
ParseUnsignedInt (char* String, s32 Length)
|
|
{
|
|
Assert(IsNumeric(*String));
|
|
parse_result Result = {};
|
|
Result.Type = ParseType_UnsignedInt;
|
|
|
|
char* Iter = String;
|
|
u32 ResultValue = 0;
|
|
for (s32 i = 0; i < Length; i++)
|
|
{
|
|
ResultValue = CharToUInt(*Iter++) + (ResultValue * 10);
|
|
}
|
|
|
|
Result.UnsignedIntValue = ResultValue;
|
|
Result.OnePastLast = Iter;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static parse_result
|
|
ParseUnsignedIntUnsafe (char* String)
|
|
{
|
|
char* Start = String;
|
|
char* End = EatNumber(String + 1);
|
|
return ParseUnsignedInt(String, End - Start);
|
|
}
|
|
|
|
static parse_result
|
|
ParseSignedInt (char* String, s32 Length)
|
|
{
|
|
Assert(Length > 0);
|
|
parse_result Result = {};
|
|
Result.Type = ParseType_SignedInt;
|
|
|
|
s32 Negative = 1;
|
|
s32 LengthRemaining = Length;
|
|
s32 ResultValue = 0;
|
|
char* Iter = String;
|
|
|
|
if (*Iter == '-') {
|
|
LengthRemaining--;
|
|
*Iter++;
|
|
Negative = -1;
|
|
}
|
|
|
|
for (s32 i = 0; i < LengthRemaining; i++)
|
|
{
|
|
ResultValue = CharToUInt(*Iter++) + (ResultValue * 10);
|
|
}
|
|
|
|
ResultValue *= Negative;
|
|
|
|
Result.SignedIntValue = ResultValue;
|
|
Result.OnePastLast = Iter;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static parse_result
|
|
ParseSignedIntUnsafe (char* String)
|
|
{
|
|
char* Start = String;
|
|
char* End = EatNumber(String + 1);
|
|
return ParseSignedInt(String, End - Start);
|
|
}
|
|
|
|
static parse_result
|
|
ParseFloat (char* String, s32 Length)
|
|
{
|
|
parse_result Result = {};
|
|
Result.Type = ParseType_Float;
|
|
|
|
s32 Negative = 1;
|
|
s32 LengthRemaining = Length;
|
|
float ResultValue = 0;
|
|
char* Iter = String;
|
|
|
|
if (*Iter == '-') {
|
|
LengthRemaining--;
|
|
*Iter++;
|
|
Negative = -1;
|
|
}
|
|
|
|
for (s32 i = 0; i < LengthRemaining; i++)
|
|
{
|
|
if (IsNumeric(*Iter))
|
|
{
|
|
ResultValue = (float)CharToUInt(*Iter++) + (ResultValue * 10);
|
|
}
|
|
else if (*Iter == '.' || *Iter == 0)
|
|
{
|
|
LengthRemaining -= i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (*Iter == '.')
|
|
{
|
|
*Iter++;
|
|
float AfterPoint = 0;
|
|
s32 PlacesAfterPoint = 0;
|
|
|
|
for (s32 i = 0; i < LengthRemaining; i++)
|
|
{
|
|
if (IsNumeric(*Iter))
|
|
{
|
|
AfterPoint = (float)CharToUInt(*Iter++) + (AfterPoint * 10);
|
|
PlacesAfterPoint++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
AfterPoint = AfterPoint / GSPow(10, PlacesAfterPoint);
|
|
ResultValue += AfterPoint;
|
|
}
|
|
|
|
ResultValue *= Negative;
|
|
|
|
Result.FloatValue = ResultValue;
|
|
Result.OnePastLast = Iter;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static parse_result
|
|
ParseFloatUnsafe (char* String)
|
|
{
|
|
char* Start = String;
|
|
char* End = EatNumber(String + 1);
|
|
return ParseFloat(String, End - Start);
|
|
}
|
|
|
|
static s32
|
|
UIntToString (u32 Int, char* String, s32 MaxLength, b32 FormatFlags = 0, s32 MinimumLength = 0)
|
|
{
|
|
s32 Remaining = Int;
|
|
char* Iter = String;
|
|
while (Remaining > 0 && (Iter - String) < MaxLength)
|
|
{
|
|
*Iter++ = '0' + (Remaining % 10);
|
|
Remaining /= 10;
|
|
}
|
|
s32 CharsCopied = Iter - String;
|
|
ReverseCharArray(String, CharsCopied);
|
|
return CharsCopied;
|
|
}
|
|
|
|
static s32
|
|
IntToString (s32 Int, char* String, s32 MaxLength, b32 FormatFlags = 0, s32 MinimumLength = 0)
|
|
{
|
|
s32 Remaining = Int;
|
|
s32 CharsCopied = 0;
|
|
|
|
char* Iter = String;
|
|
|
|
bool Negative = Remaining < 0;
|
|
Remaining = GSAbs(Remaining);
|
|
|
|
if (Remaining > 0)
|
|
{
|
|
while (Remaining > 0 && CharsCopied < MaxLength)
|
|
{
|
|
*Iter++ = '0' + (Remaining % 10);
|
|
Remaining /= 10;
|
|
CharsCopied++;
|
|
}
|
|
}
|
|
else if (Remaining == 0)
|
|
{
|
|
*Iter++ = '0';
|
|
}
|
|
|
|
if (Negative)
|
|
{
|
|
*Iter++ = '-';
|
|
CharsCopied++;
|
|
}
|
|
|
|
ReverseCharArray(String, CharsCopied);
|
|
return CharsCopied;
|
|
}
|
|
|
|
static s32
|
|
IntToString (s32 Int, char* String, s32 MaxLength, s32 Offset, b32 FormatFlags = 0, s32 MinimumWidth = 0)
|
|
{
|
|
char* StringStart = String + Offset;
|
|
s32 LengthWritten = IntToString(Int, StringStart, MaxLength - Offset);
|
|
return LengthWritten;
|
|
}
|
|
|
|
static s32
|
|
FloatToString(float Float, char *String, s32 MaxLength, s32 AfterPoint = 0, b32 FormatFlags = 0, s32 MinimumWidth = 0)
|
|
{
|
|
s32 IPart = (s32)Float;
|
|
float FPart = GSAbs(Float - (float)IPart);
|
|
|
|
s32 i = IntToString(IPart, String, MaxLength);
|
|
|
|
if (AfterPoint > 1)
|
|
{
|
|
String[i++] = '.';
|
|
|
|
s32 FPartInt = FPart * GSPow(10, AfterPoint);
|
|
i += IntToString(FPartInt, String, MaxLength, i, 0, 0);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// PrintF
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static void
|
|
OutChar (string* String, char C)
|
|
{
|
|
if (String->Length < String->Max)
|
|
{
|
|
String->Memory[String->Length] = C;
|
|
String->Length++;
|
|
}
|
|
}
|
|
|
|
char OctalDigits[] = "01234567";
|
|
char DecimalDigits[] = "0123456789";
|
|
char HexDigits[] = "0123456789ABCDEF";
|
|
|
|
static void
|
|
U64ToASCII (string* String, u64 Value, s32 Base, char* Digits)
|
|
{
|
|
u64 ValueRemaining = Value;
|
|
char* Start = String->Memory + String->Length;
|
|
do {
|
|
s32 DigitsIndex = ValueRemaining % Base;
|
|
char Digit = Digits[DigitsIndex];
|
|
OutChar(String, Digit);
|
|
ValueRemaining /= Base;
|
|
}while (ValueRemaining);
|
|
char* End = String->Memory + String->Length;
|
|
|
|
while (Start < End)
|
|
{
|
|
End--;
|
|
char Temp = *End;
|
|
*End = *Start;
|
|
*Start = Temp;
|
|
*Start++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
F64ToASCII (string* String, r64 Value, s32 Precision)
|
|
{
|
|
if (Value < 0)
|
|
{
|
|
OutChar(String, '-');
|
|
Value = -Value;
|
|
}
|
|
|
|
u64 IntegerPart = (u64)Value;
|
|
Value -= IntegerPart;
|
|
|
|
U64ToASCII(String, IntegerPart, 10, DecimalDigits);
|
|
|
|
OutChar(String, '.');
|
|
|
|
for (s32 i = 0; i < Precision; i++)
|
|
{
|
|
Value *= 10.f;
|
|
u32 DecimalPlace = Value;
|
|
Value -= DecimalPlace;
|
|
OutChar(String, DecimalDigits[DecimalPlace]);
|
|
}
|
|
}
|
|
|
|
internal s64
|
|
ReadVarArgsSignedInteger (s32 Width, va_list* Args)
|
|
{
|
|
s64 Result = 0;
|
|
switch (Width)
|
|
{
|
|
case 1: { Result = (s64)va_arg(*Args, s8); } break;
|
|
case 2: { Result = (s64)va_arg(*Args, s16); } break;
|
|
case 4: { Result = (s64)va_arg(*Args, s32); } break;
|
|
case 8: { Result = (s64)va_arg(*Args, s64); } break;
|
|
InvalidDefaultCase;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
internal r64
|
|
ReadVarArgsUnsignedInteger (s32 Width, va_list* Args)
|
|
{
|
|
u64 Result = 0;
|
|
switch (Width)
|
|
{
|
|
case 1: { Result = (u64)va_arg(*Args, u8); } break;
|
|
case 2: { Result = (u64)va_arg(*Args, u16); } break;
|
|
case 4: { Result = (u64)va_arg(*Args, u32); } break;
|
|
case 8: { Result = (u64)va_arg(*Args, u64); } break;
|
|
InvalidDefaultCase;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
internal r64
|
|
ReadVarArgsFloat (s32 Width, va_list* Args)
|
|
{
|
|
r64 Result = 0;
|
|
switch (Width)
|
|
{
|
|
case 4: { Result = (r64)va_arg(*Args, r64); } break;
|
|
case 8: { Result = (r64)va_arg(*Args, r64); } break;
|
|
InvalidDefaultCase;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
internal s32
|
|
PrintFArgsList (char* Dest, s32 DestMax, char* Format, va_list Args)
|
|
{
|
|
char* DestAt = Dest;
|
|
|
|
char* FormatAt = Format;
|
|
while (*FormatAt)
|
|
{
|
|
if (FormatAt[0] != '%')
|
|
{
|
|
*DestAt++ = *FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == '%' && FormatAt[1] == '%') // Print the % symbol
|
|
{
|
|
*DestAt++ = *FormatAt++;
|
|
}
|
|
else
|
|
{
|
|
FormatAt++;
|
|
|
|
// Flags
|
|
if (FormatAt[0] == '-')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == '+')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == ' ')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == '#')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == '0')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
|
|
// Width
|
|
b32 WidthSpecified = false;
|
|
s32 Width = 0;
|
|
|
|
if (IsNumeric(FormatAt[0]))
|
|
{
|
|
WidthSpecified = true;
|
|
parse_result Parse = ParseSignedIntUnsafe(FormatAt);
|
|
FormatAt = Parse.OnePastLast;
|
|
Width = Parse.SignedIntValue;
|
|
}
|
|
else if (FormatAt[0] == '*')
|
|
{
|
|
WidthSpecified = true;
|
|
Width = va_arg(Args, s32);
|
|
Assert(Width >= 0);
|
|
FormatAt++;
|
|
}
|
|
|
|
// Precision
|
|
b32 PrecisionSpecified = false;
|
|
s32 Precision = 0;
|
|
|
|
if (FormatAt[0] == '.')
|
|
{
|
|
FormatAt++;
|
|
if (IsNumeric(FormatAt[0]))
|
|
{
|
|
PrecisionSpecified = true;
|
|
parse_result Parse = ParseSignedIntUnsafe(FormatAt);
|
|
FormatAt = Parse.OnePastLast;
|
|
Precision = Parse.SignedIntValue;
|
|
}
|
|
else if (FormatAt[0] == '*')
|
|
{
|
|
PrecisionSpecified = true;
|
|
Precision = va_arg(Args, s32);
|
|
Assert(Precision >= 0);
|
|
FormatAt++;
|
|
}
|
|
}
|
|
|
|
// Length
|
|
b32 LengthSpecified = false;
|
|
s32 Length = 4;
|
|
|
|
if (FormatAt[0] == 'h' && FormatAt[1] == 'h')
|
|
{
|
|
LengthSpecified = true;
|
|
LengthSpecified = 1;
|
|
FormatAt += 2;
|
|
}
|
|
else if (FormatAt[0] == 'h')
|
|
{
|
|
LengthSpecified = true;
|
|
LengthSpecified = 2;
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == 'l' && FormatAt[1] == 'l')
|
|
{
|
|
LengthSpecified = true;
|
|
LengthSpecified = 8;
|
|
FormatAt += 2;
|
|
}
|
|
else if (FormatAt[0] == 'l')
|
|
{
|
|
LengthSpecified = true;
|
|
LengthSpecified = 4;
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == 'j')
|
|
{
|
|
LengthSpecified = true;
|
|
LengthSpecified = 8;
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == 'z')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == 't')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
else if (FormatAt[0] == 'L')
|
|
{
|
|
FormatAt++;
|
|
}
|
|
|
|
// Format Specifier
|
|
s32 DestLengthRemaining = DestMax - (DestAt - Dest);
|
|
|
|
char Temp[64];
|
|
string TempDest = MakeString(Temp, 0, 64);
|
|
|
|
if (FormatAt[0] == 'd' || FormatAt[0] == 'i')
|
|
{
|
|
s64 SignedInt = ReadVarArgsSignedInteger(Length, &Args);
|
|
if (SignedInt < 0)
|
|
{
|
|
OutChar(&TempDest, '-');
|
|
}
|
|
U64ToASCII(&TempDest, (u64)SignedInt, 10, DecimalDigits);
|
|
}
|
|
else if (FormatAt[0] == 'u')
|
|
{
|
|
u32 UnsignedInt = ReadVarArgsUnsignedInteger(Length, &Args);
|
|
U64ToASCII(&TempDest, UnsignedInt, 10, DecimalDigits);
|
|
}
|
|
else if (FormatAt[0] == 'o')
|
|
{
|
|
u32 UnsignedInt = ReadVarArgsUnsignedInteger(Length, &Args);
|
|
U64ToASCII(&TempDest, UnsignedInt, 8, OctalDigits);
|
|
}
|
|
else if (FormatAt[0] == 'x' || FormatAt[0] == 'X')
|
|
{
|
|
u32 UnsignedInt = ReadVarArgsUnsignedInteger(Length, &Args);
|
|
U64ToASCII(&TempDest, UnsignedInt, 16, HexDigits);
|
|
}
|
|
else if (FormatAt[0] == 'f' || FormatAt[0] == 'F')
|
|
{
|
|
r64 Float = ReadVarArgsFloat(Length, &Args);
|
|
s32 AfterPoint = 6;
|
|
if (PrecisionSpecified)
|
|
{
|
|
AfterPoint = Precision;
|
|
}
|
|
F64ToASCII(&TempDest, Float, AfterPoint);
|
|
}
|
|
else if (FormatAt[0] == 'c')
|
|
{
|
|
char InsertChar = va_arg(Args, char);
|
|
OutChar(&TempDest, InsertChar);
|
|
}
|
|
else if (FormatAt[0] == 's')
|
|
{
|
|
char* InsertString = va_arg(Args, char*);
|
|
|
|
s32 InsertStringLength = CharArrayLength(InsertString);
|
|
if (PrecisionSpecified)
|
|
{
|
|
InsertStringLength = GSMin(InsertStringLength, Precision);
|
|
}
|
|
InsertStringLength = GSMin(DestLengthRemaining, InsertStringLength);
|
|
|
|
for (s32 c = 0; c < InsertStringLength; c++)
|
|
{
|
|
OutChar(&TempDest, *InsertString++);
|
|
}
|
|
}
|
|
else if (FormatAt[0] == 'p')
|
|
{
|
|
// TODO(Peter): Pointer Address
|
|
}
|
|
else
|
|
{
|
|
// NOTE(Peter): Non-specifier character found
|
|
InvalidCodePath;
|
|
}
|
|
|
|
for (s32 i = 0; i < TempDest.Length; i++)
|
|
{
|
|
*DestAt++ = TempDest.Memory[i];
|
|
}
|
|
|
|
*FormatAt++;
|
|
}
|
|
}
|
|
|
|
s32 FormattedLength = DestAt - Dest;
|
|
return FormattedLength;
|
|
}
|
|
|
|
internal void
|
|
PrintF (string* String, char* Format, ...)
|
|
{
|
|
va_list Args;
|
|
va_start(Args, Format);
|
|
String->Length = PrintFArgsList(String->Memory, String->Max, Format, Args);
|
|
va_end(Args);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// String Memory Function Definitions
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static s32
|
|
CalculateSlotCountFromSize (s32 RequestedSize, s32 SlotSize)
|
|
{
|
|
s32 SlotCount = RequestedSize / SlotSize;
|
|
if (SlotCount * SlotSize < RequestedSize)
|
|
{
|
|
SlotCount += 1;
|
|
}
|
|
return SlotCount;
|
|
}
|
|
|
|
static bool
|
|
SlotsAreContiguous (slot_header* First, slot_header* Second)
|
|
{
|
|
bool Result = false;
|
|
u8* FirstSlotNextAddress = (u8*)First + First->Size;
|
|
u8* SecondAddress = (u8*)Second;
|
|
Result = FirstSlotNextAddress == SecondAddress;
|
|
return Result;
|
|
}
|
|
|
|
static contiguous_slot_count_result
|
|
CountContiguousSlots (slot_header* First)
|
|
{
|
|
Assert(First != 0);
|
|
|
|
contiguous_slot_count_result Result = {};
|
|
Result.Count = 1;
|
|
|
|
slot_header* IterPrev = First;
|
|
slot_header* Iter = First->Next;
|
|
while (Iter && SlotsAreContiguous(IterPrev, Iter))
|
|
{
|
|
Result.Count++;
|
|
IterPrev = Iter;
|
|
Iter = Iter->Next;
|
|
}
|
|
|
|
Result.LastContiguousSlot = IterPrev;
|
|
return Result;
|
|
}
|
|
|
|
static slot_header*
|
|
GetSlotAtOffset(slot_header* First, s32 Offset)
|
|
{
|
|
slot_header* Iter = First;
|
|
s32 Count = 0;
|
|
while (Count < Offset && Iter)
|
|
{
|
|
Iter = Iter->Next;
|
|
Count++;
|
|
}
|
|
return Iter;
|
|
}
|
|
|
|
static slot_header*
|
|
InsertSlotIntoList (slot_header* NewSlot, slot_header* ListStart)
|
|
{
|
|
slot_header* List = ListStart;
|
|
if (NewSlot < List)
|
|
{
|
|
NewSlot->Next = List;
|
|
List = NewSlot;
|
|
}
|
|
else
|
|
{
|
|
slot_header* PrevIter = List;
|
|
slot_header* Iter = List->Next;
|
|
while (Iter && NewSlot > Iter)
|
|
{
|
|
PrevIter = Iter;
|
|
Iter = Iter->Next;
|
|
}
|
|
|
|
Assert(PrevIter);
|
|
if (PrevIter)
|
|
{
|
|
PrevIter->Next = NewSlot;
|
|
}
|
|
|
|
if (Iter)
|
|
{
|
|
NewSlot->Next = Iter;
|
|
}
|
|
}
|
|
return List;
|
|
}
|
|
|
|
static void
|
|
AllocStringFromStringArena (string* String, s32 Size, slot_arena* Storage)
|
|
{
|
|
s32 SlotCount = CalculateSlotCountFromSize(Size, Storage->SlotSize);
|
|
slot_header* Slot = Storage->FreeList;
|
|
slot_header* PrevSlot = 0;
|
|
while (Slot)
|
|
{
|
|
contiguous_slot_count_result ContiguousSlots = CountContiguousSlots(Slot);
|
|
if (ContiguousSlots.Count >= SlotCount)
|
|
{
|
|
slot_header* NextStartSlot = GetSlotAtOffset(Slot, SlotCount);
|
|
if (PrevSlot)
|
|
{
|
|
PrevSlot->Next = NextStartSlot;
|
|
}
|
|
else
|
|
{
|
|
Storage->FreeList = NextStartSlot;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PrevSlot = Slot;
|
|
Slot = Slot->Next;
|
|
}
|
|
}
|
|
|
|
if (Slot)
|
|
{
|
|
String->Memory = (char*)Slot;
|
|
GSZeroMemory((u8*)String->Memory, SlotCount * Storage->SlotSize);
|
|
String->Max = SlotCount * Storage->SlotSize;
|
|
String->Length = 0;
|
|
}
|
|
}
|
|
|
|
static string
|
|
AllocStringFromStringArena (s32 Size, slot_arena* Storage)
|
|
{
|
|
string Result = {0};
|
|
AllocStringFromStringArena(&Result, Size, Storage);
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
FreeToStringArena (string* String, slot_arena* Storage)
|
|
{
|
|
u8* Base = (u8*)(String->Memory);
|
|
u8* End = Base + String->Max - 1;
|
|
u8* MemoryEnd = Storage->Memory + (Storage->SlotSize * Storage->SlotCount);
|
|
Assert((Base >= Storage->Memory) && (End < MemoryEnd));
|
|
Assert((String->Max % Storage->SlotSize) == 0);
|
|
|
|
s32 SizeReclaimed = 0;
|
|
slot_header* Slot = (slot_header*)Base;
|
|
while (SizeReclaimed < String->Max)
|
|
{
|
|
Slot->Size = Storage->SlotSize;
|
|
Storage->FreeList = InsertSlotIntoList(Slot, Storage->FreeList);
|
|
SizeReclaimed += Storage->SlotSize;
|
|
Slot = (slot_header*)(Base + SizeReclaimed);
|
|
}
|
|
|
|
String->Memory = 0;
|
|
String->Length = 0;
|
|
String->Max = 0;
|
|
}
|
|
|
|
static void
|
|
ReallocFromStringArena (string* String, s32 NewSize, slot_arena* Storage)
|
|
{
|
|
string NewString = AllocStringFromStringArena(NewSize, Storage);
|
|
CopyStringTo(*String, &NewString);
|
|
FreeToStringArena(String, Storage);
|
|
*String = NewString;
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
|
|
void DEBUGPrintChars (string* String, s32 Count)
|
|
{
|
|
char* Iter = String->Memory;
|
|
for (int i = 0; i < Count; i++)
|
|
{
|
|
*Iter++ = (char)('A' + i);
|
|
}
|
|
String->Length = Count;
|
|
}
|
|
|
|
#ifdef DEBUG_GS_STRING
|
|
|
|
#include <stdlib.h>
|
|
|
|
static void
|
|
TestStrings()
|
|
{
|
|
|
|
slot_arena StringArena = {};
|
|
|
|
s32 TestCount = 0;
|
|
s32 SuccessCount = 0;
|
|
|
|
DebugPrint("\n\n-------------------------------------------------\n Begin Testing Strings\n\n\n");
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Char Functions
|
|
|
|
char ForwardArray[] = "Hello, Sailor";
|
|
char BackwardArray[] = "roliaS ,olleH";
|
|
TestClean(CharArraysEqual(ForwardArray, 13, ForwardArray, 13), "String Equality");
|
|
TestClean(!CharArraysEqual(ForwardArray, 13, BackwardArray, 13), "String Equality");
|
|
|
|
TestClean(IndexOfChar(ForwardArray, 0, ',') == 5, "Index Of Char");
|
|
TestClean(IndexOfChar(ForwardArray, 5, 'l') == 10, "Index of Char (skipping first 5)");
|
|
TestClean(FastReverseIndexOfChar(ForwardArray, 13, 0, 'o') == 11, "Fast Reverse Index Of Char");
|
|
TestClean(ReverseIndexOfChar(ForwardArray, 0, 'o') == 11, "Reverse Index of Char");
|
|
TestClean(ReverseIndexOfChar(ForwardArray, 3, 'o') == 4, "Reverse Index of Char (skipping last 3)");
|
|
TestClean(LastIndexOfChar(ForwardArray, 'o') == 11, "Last Index of Char");
|
|
|
|
ReverseCharArray(ForwardArray, 13);
|
|
TestClean(CharArraysEqual(ForwardArray, 13, BackwardArray, 13), "Reversing Char Array");
|
|
|
|
char UIntString[] = "1234";
|
|
u32 UIntValue = 1234;
|
|
u32 ParsedUInt = ParseUnsignedInt(UIntString, 4);
|
|
TestClean((ParsedUInt == UIntValue), "String To U32");
|
|
char StringifiedUInt[4] = {};
|
|
UIntToString(UIntValue, StringifiedUInt, 4);
|
|
TestClean(CharArraysEqual(UIntString, 4, StringifiedUInt, 4), "U32 To String");
|
|
|
|
char IntString[] = "-1234";
|
|
s32 IntValue = -1234;
|
|
s32 ParsedInt = ParseSignedInt(IntString, 5);
|
|
TestClean((ParsedInt == IntValue), "String To S32");
|
|
char StringifiedInt[5] = {};
|
|
IntToString(IntValue, StringifiedInt, 5);
|
|
TestClean(CharArraysEqual(IntString, 5, StringifiedInt, 5), "S32 to String");
|
|
|
|
char FloatString[] = "-1234.125";
|
|
float FloatValue = -1234.125f;
|
|
float ParsedFloat = ParseFloat(FloatString, 8);
|
|
TestClean((ParsedFloat == FloatValue), "String To Float");
|
|
char StringifiedFloat[10] = {};
|
|
FloatToString(FloatValue, StringifiedFloat, 10, 3);
|
|
TestClean(CharArraysEqual(FloatString, 8, StringifiedFloat, 8), "Float To String");
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
StringArena.SlotSize = 256;
|
|
StringArena.SlotCount = 32;
|
|
StringArena.Memory = malloc(StringArena.SlotSize * StringArena.SlotCount);
|
|
slot_header* PrevSlotHeader = 0;
|
|
for (int i = StringArena.SlotCount - 1; i >= 0; i--)
|
|
{
|
|
u8* SlotBase = StringArena.Memory + (i * StringArena.SlotSize);
|
|
slot_header* SlotHeader = (slot_header*)SlotBase;
|
|
SlotHeader->Size = StringArena.SlotSize;
|
|
SlotHeader->Next = PrevSlotHeader;
|
|
|
|
// TEST(peter): Should be true always, except on the first iteration, when there is no next slot
|
|
bool Contiguity = SlotsAreContiguous(SlotHeader, PrevSlotHeader);
|
|
TestClean((Contiguity || SlotHeader->Next == 0), "Contiguous Arenas");
|
|
|
|
PrevSlotHeader = SlotHeader;
|
|
}
|
|
StringArena.FreeList = PrevSlotHeader;
|
|
|
|
// TEST(peter): Count Should equal StringArena.SlotCount
|
|
s32 ContiguousSlotsCountBefore = CountContiguousSlots(StringArena.FreeList).Count;
|
|
TestClean((ContiguousSlotsCountBefore == StringArena.SlotCount), "Contiguous Arenas");
|
|
|
|
// TEST(peter): Should be false
|
|
bool Contiguity = SlotsAreContiguous(StringArena.FreeList, StringArena.FreeList->Next->Next);
|
|
Contiguity = SlotsAreContiguous(StringArena.FreeList->Next->Next, StringArena.FreeList);
|
|
TestClean(!Contiguity, "Non Contiguous Arenas");
|
|
|
|
s32 Slots = CalculateSlotCountFromSize(10, 256);
|
|
TestClean(Slots == 1, "Slot Sizing");
|
|
Slots = CalculateSlotCountFromSize(256, 256);
|
|
TestClean(Slots == 1, "Slot Sizing");
|
|
Slots = CalculateSlotCountFromSize(345, 256);
|
|
TestClean(Slots == 2, "Slot Sizing");
|
|
Slots = CalculateSlotCountFromSize(1024, 256);
|
|
TestClean(Slots == 4, "Slot Sizing");
|
|
|
|
slot_header* HeaderTen = GetSlotAtOffset(StringArena.FreeList, 10);
|
|
slot_header* HeaderThree = GetSlotAtOffset(StringArena.FreeList, 3);
|
|
slot_header* HeaderFive = GetSlotAtOffset(StringArena.FreeList, 5);
|
|
|
|
string StringA = AllocStringFromStringArena(10, &StringArena);
|
|
string StringB = AllocStringFromStringArena(345, &StringArena);
|
|
|
|
#if 0
|
|
// TEST(peter): Should TestClean
|
|
u8* RandomMemory = (u8*)malloc(256);
|
|
string RandomMemString = {};
|
|
RandomMemString.Memory = (char*)RandomMemory;
|
|
RandomMemString.Max = 256;
|
|
FreeToStringArena(&RandomMemString, &StringArena);
|
|
#endif
|
|
FreeToStringArena(&StringA, &StringArena);
|
|
FreeToStringArena(&StringB, &StringArena);
|
|
// TEST(peter): After freeing both allocations, ContiguousSlotCountBefore and ContiguousSlotCountAfter should be equal
|
|
s32 ContiguousSlotCountAfter = CountContiguousSlots(StringArena.FreeList).Count;
|
|
TestClean(ContiguousSlotCountAfter == ContiguousSlotsCountBefore, "Add and REmove Slots from Arena");
|
|
|
|
// TEST(peter): Set up a free list where the first element is too small, so it has to traverse to find an appropriately
|
|
// sized block
|
|
// The slots will look list [256][used][256][256][256] etc..
|
|
StringA = AllocStringFromStringArena(256, &StringArena);
|
|
StringB = AllocStringFromStringArena(256, &StringArena);
|
|
FreeToStringArena(&StringA, &StringArena);
|
|
u32 Contiguous = CountContiguousSlots(StringArena.FreeList).Count; // Should = 1;
|
|
string StringC = AllocStringFromStringArena(512, &StringArena);
|
|
slot_header* HeaderC = (slot_header*)(StringC.Memory);
|
|
|
|
string ReallocTestString = AllocStringFromStringArena(256, &StringArena);
|
|
DEBUGPrintChars(&ReallocTestString, 24);
|
|
ReallocFromStringArena(&ReallocTestString, 512, &StringArena);
|
|
|
|
|
|
string TestString = AllocStringFromStringArena(10, &StringArena);
|
|
DEBUGPrintChars(&TestString, TestString.Max);
|
|
ReallocFromStringArena(&TestString, 20, &StringArena);
|
|
DEBUGPrintChars(&TestString, TestString.Max);
|
|
ReallocFromStringArena(&TestString, 10, &StringArena);
|
|
FreeToStringArena(&TestString, &StringArena);
|
|
|
|
string EqualityStringA = AllocStringFromStringArena(345, &StringArena);
|
|
string EqualityStringB = AllocStringFromStringArena(415, &StringArena);
|
|
// Equality should succeed despite length differences
|
|
string EqualityStringC = AllocStringFromStringArena(256, &StringArena);
|
|
string EqualityStringD = AllocStringFromStringArena(256, &StringArena); // Equality should fail
|
|
string EqualityStringEmpty = {};
|
|
|
|
DEBUGPrintChars(&EqualityStringA, 24);
|
|
DEBUGPrintChars(&EqualityStringB, 24);
|
|
DEBUGPrintChars(&EqualityStringC, 24);
|
|
DEBUGPrintChars(&EqualityStringD, 12);
|
|
|
|
bool ABEquality = StringsEqual(EqualityStringA, EqualityStringB); // Should Succeed
|
|
bool ACEquality = StringsEqual(EqualityStringA, EqualityStringC); // Should Succeed
|
|
bool ADEquality = StringsEqual(EqualityStringA, EqualityStringD); // Should Fail
|
|
bool AEEquality = StringsEqual(EqualityStringA, EqualityStringEmpty); // Should Fail
|
|
|
|
TestClean(ABEquality, "String Equality");
|
|
TestClean(ACEquality, "String Equality");
|
|
TestClean(!ADEquality, "String Equality");
|
|
TestClean(!AEEquality, "String Equality");
|
|
|
|
string CatStringA = AllocStringFromStringArena(256, &StringArena);
|
|
SetStringToCharArray(&CatStringA, "Hello ");
|
|
string CatStringB = AllocStringFromStringArena(512, &StringArena);
|
|
SetStringToCharArray(&CatStringB, "Sailor!");
|
|
string CatStringResult = AllocStringFromStringArena(512, &StringArena);
|
|
SetStringToCharArray(&CatStringResult, "Hello Sailor!");
|
|
ConcatString(&CatStringA, CatStringB);
|
|
TestClean(StringsEqual(CatStringA, CatStringResult), "Cat Strings");
|
|
|
|
s32 FirstSpaceIndex = FindFirstChar(CatStringA, ' ');
|
|
TestClean(FirstSpaceIndex == 5, "First Index");
|
|
|
|
SetStringToChar(&CatStringB, 'B', 5);
|
|
TestClean(StringEqualsCharArray(CatStringB, "BBBBB"), "SetStringToChar");
|
|
|
|
|
|
DebugPrint("Results: Passed %d / %d\n\n\n", SuccessCount, TestCount);
|
|
}
|
|
#endif // DEBUG_GS_STRING
|
|
|
|
#endif // DEBUG
|
|
|
|
#define GS_STRING_H
|
|
#endif // GS_STRING_H
|