327 lines
11 KiB
C++
327 lines
11 KiB
C++
static config *CreateConfig(void)
|
|
{
|
|
memory_arena *Arena = ArenaAllocate(Gigabytes(1));
|
|
config *Config = PushStruct(Arena, config);
|
|
Config->Arena = Arena;
|
|
return(Config);
|
|
}
|
|
|
|
static config_entry *Config_FindEntryByName(config *Config, string Name)
|
|
{
|
|
config_entry *Result = 0;
|
|
|
|
u32 BucketSlot = HashString(Name) % ArrayCount(Config->EntryBuckets);
|
|
config_entry_bucket *Bucket = Config->EntryBuckets + BucketSlot;
|
|
|
|
for(config_entry *Entry = Bucket->First;
|
|
Entry != 0;
|
|
Entry = Entry->Next)
|
|
{
|
|
if(AreEqual(Entry->Name, Name))
|
|
{
|
|
Result = Entry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(Result);
|
|
}
|
|
|
|
static void Config_BindEntry(config *Config, string Name, config_entry_type Type, void *Target)
|
|
{
|
|
config_entry *Entry = Config_FindEntryByName(Config, Name);
|
|
if(!Entry)
|
|
{
|
|
Entry = PushStruct(Config->Arena, config_entry);
|
|
Entry->Name = PushString(Config->Arena, Name);
|
|
Entry->Type = Type;
|
|
|
|
u32 BucketSlot = HashString(Name) % ArrayCount(Config->EntryBuckets);
|
|
config_entry_bucket *Bucket = Config->EntryBuckets + BucketSlot;
|
|
|
|
DLLInsertLast(Bucket->First, Bucket->Last, Entry);
|
|
|
|
if(Config->LastInternal)
|
|
{
|
|
Config->LastInternal->NextInternal = Entry;
|
|
Config->LastInternal = Entry;
|
|
}
|
|
else
|
|
{
|
|
Config->FirstInternal = Config->LastInternal = Entry;
|
|
}
|
|
}
|
|
|
|
Assert(Entry->Type == Type);
|
|
Entry->Target = Target;
|
|
}
|
|
|
|
inline void Config_BindS32(config *Config, string Name, s32 *Target, s32 Default)
|
|
{
|
|
*Target = Default;
|
|
Config_BindEntry(Config, Name, Config_Entry_S32, Target);
|
|
}
|
|
|
|
inline void Config_BindS64(config *Config, string Name, s64 *Target, s64 Default)
|
|
{
|
|
*Target = Default;
|
|
Config_BindEntry(Config, Name, Config_Entry_S64, Target);
|
|
}
|
|
|
|
inline void Config_BindB32(config *Config, string Name, b32 *Target, b32 Default)
|
|
{
|
|
*Target = Default;
|
|
Config_BindEntry(Config, Name, Config_Entry_B32, Target);
|
|
}
|
|
|
|
static void Config_ParseError(string Message, string FileText, s64 Offset, memory_arena *Arena)
|
|
{
|
|
text_point Point = TextPointFromOffset(FileText, Offset);
|
|
string String = PushFormat(Arena, "Config: At %i:%i - %S", Point.Line, Point.Column, Message);
|
|
Platform.ShowMessage(String, Platform_Message_Warning);
|
|
}
|
|
|
|
static void Config_ReadFile(config *Config, string Path)
|
|
{
|
|
temporary_memory Scratch = GetScratch();
|
|
|
|
//- sixten: read & tokenize input file
|
|
string Text = Platform_ReadEntireFile(Scratch.Arena, Path);
|
|
tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, Path, Text, TokenGroup_Whitespace|TokenGroup_Comment);
|
|
token_array Tokens = TokenizeResult.Tokens;
|
|
|
|
// sixten: parse context
|
|
config_parse_list FullPath = {};
|
|
config_parse_mode ParseMode = ConfigParseMode_Main;
|
|
|
|
//- sixten: parse tokens
|
|
token *TokensStart = Tokens.Tokens;
|
|
token *TokensEnd = Tokens.Tokens + Tokens.Count;
|
|
token *Token = TokensStart;
|
|
for(;Token < TokensEnd;)
|
|
{
|
|
string TokenString = Substring(Text, Token->Range);
|
|
|
|
//- sixten: get next name
|
|
if(ParseMode == ConfigParseMode_Main && Token->Flags & TokenFlag_Identifier)
|
|
{
|
|
Config_ParseListPush(Scratch.Arena, &FullPath, TokenString);
|
|
ParseMode = ConfigParseMode_ScanForCurlyOpenOrEquals;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for curly close
|
|
if(ParseMode == ConfigParseMode_Main && Token->Flags & TokenFlag_Reserved && AreEqual(TokenString, StrLit("}")))
|
|
{
|
|
Config_ParseListPop(&FullPath);
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for curly open
|
|
if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Flags & TokenFlag_Reserved && AreEqual(TokenString, StrLit("{")))
|
|
{
|
|
ParseMode = ConfigParseMode_Main;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for equals
|
|
if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Flags & TokenFlag_Symbol && AreEqual(TokenString, StrLit("=")))
|
|
{
|
|
ParseMode = ConfigParseMode_ScanForValue;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for semicolon
|
|
if(ParseMode == ConfigParseMode_ScanForSemicolon && Token->Flags & TokenFlag_Reserved && AreEqual(TokenString, StrLit(";")))
|
|
{
|
|
ParseMode = ConfigParseMode_Main;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for boolean value
|
|
if(ParseMode == ConfigParseMode_ScanForValue && Token->Flags & TokenFlag_Identifier && (AreEqual(TokenString, StrLit("true")) || AreEqual(TokenString, StrLit("false"))))
|
|
{
|
|
string FullName = Config_ParseListJoin(Scratch.Arena, &FullPath);
|
|
config_entry *Entry = Config_FindEntryByName(Config, FullName);
|
|
if(Entry)
|
|
{
|
|
b32 Value = AreEqual(TokenString, StrLit("true"));
|
|
Assert(Entry->Type == Config_Entry_B32);
|
|
*(b32 *)Entry->Target = Value;
|
|
}
|
|
Config_ParseListPop(&FullPath);
|
|
ParseMode = ConfigParseMode_ScanForSemicolon;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: scan for integer value
|
|
if(ParseMode == ConfigParseMode_ScanForValue && Token->Flags & TokenFlag_Numeric)
|
|
{
|
|
string FullName = Config_ParseListJoin(Scratch.Arena, &FullPath);
|
|
config_entry *Entry = Config_FindEntryByName(Config, FullName);
|
|
if(Entry)
|
|
{
|
|
s64 Value = ConvertStringToS64(TokenString);
|
|
if(Entry->Type == Config_Entry_S32)
|
|
{
|
|
*(s32 *)Entry->Target = Value;
|
|
}
|
|
else if(Entry->Type == Config_Entry_S64)
|
|
{
|
|
*(s64 *)Entry->Target = Value;
|
|
}
|
|
else
|
|
{
|
|
InvalidCodepath;
|
|
}
|
|
}
|
|
Config_ParseListPop(&FullPath);
|
|
ParseMode = ConfigParseMode_ScanForSemicolon;
|
|
Token += 1;
|
|
goto TokenConsumed;
|
|
}
|
|
|
|
//- sixten: if the token has not been consumed, something's gone wrong
|
|
{
|
|
string ErrorMessage = StrLit("Unknown parse error");
|
|
//- sixten: determine error message
|
|
switch(ParseMode)
|
|
{
|
|
case ConfigParseMode_Main: { ErrorMessage = StrLit("Expected identifier or '}'"); } break;
|
|
case ConfigParseMode_ScanForCurlyOpenOrEquals: { ErrorMessage = StrLit("Expected '{' or '='") ; } break;
|
|
case ConfigParseMode_ScanForValue: { ErrorMessage = StrLit("Expected value"); } break;
|
|
case ConfigParseMode_ScanForSemicolon: { ErrorMessage = StrLit("Expected ';'"); } break;
|
|
}
|
|
|
|
Config_ParseError(ErrorMessage, Text, Token->Range.Min, Scratch.Arena);
|
|
Token += 1;
|
|
}
|
|
|
|
TokenConsumed:;
|
|
}
|
|
|
|
ReleaseScratch(Scratch);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ sixten: Config Parse Type Functions
|
|
static void Config_ParseListPush(memory_arena *Arena, config_parse_list *List, string Name)
|
|
{
|
|
config_parse_node *Node = PushStruct(Arena, config_parse_node);
|
|
Node->Name = Name;
|
|
List->TotalCountPlusOne += Name.Count + 1;
|
|
DLLInsertLast(List->First, List->Last, Node);
|
|
}
|
|
|
|
static void Config_ParseListPop(config_parse_list *List)
|
|
{
|
|
config_parse_node *Node = List->Last;
|
|
if(Node)
|
|
{
|
|
List->TotalCountPlusOne -= Node->Name.Count + 1;
|
|
DLLRemove(List->First, List->Last, Node);
|
|
}
|
|
}
|
|
|
|
static string Config_ParseListJoin(memory_arena *Arena, config_parse_list *List)
|
|
{
|
|
s64 TotalCount = List->TotalCountPlusOne - 1;
|
|
string Result = MakeString(PushArray(Arena, u8, List->TotalCountPlusOne), TotalCount);
|
|
s64 Index = 0;
|
|
for(config_parse_node *Node = List->First; Node != 0; Node = Node->Next)
|
|
{
|
|
Copy(Result.Data + Index, Node->Name.Data, Node->Name.Count);
|
|
Index += Node->Name.Count;
|
|
if(Node->Next)
|
|
{
|
|
Result.Data[Index] = '/';
|
|
Index += 1;
|
|
}
|
|
}
|
|
return(Result);
|
|
}
|
|
|
|
static void Config_WriteFile(config *Config, string Path)
|
|
{
|
|
string_list Out = {};
|
|
temporary_memory Scratch = GetScratch();
|
|
|
|
string LastDir = MakeString(0, 0);
|
|
for(config_entry *Entry = Config->FirstInternal;
|
|
Entry != 0;
|
|
Entry = Entry->NextInternal)
|
|
{
|
|
s64 LastSlash = LastIndexOf(Entry->Name, '/');
|
|
Assert(LastSlash != -1);
|
|
|
|
string Dir = Prefix(Entry->Name, LastSlash);
|
|
string Name = Suffix(Entry->Name, Entry->Name.Count - LastSlash - 1);
|
|
|
|
if(!AreEqual(Dir, LastDir))
|
|
{
|
|
if(!AreEqual(LastDir, MakeString(0, 0)))
|
|
{
|
|
AppendString(&Out, StrLit("}\n\n"), Scratch.Arena);
|
|
}
|
|
|
|
AppendString(&Out, Dir, Scratch.Arena);
|
|
AppendString(&Out, StrLit("\n{\n"), Scratch.Arena);
|
|
|
|
LastDir = Dir;
|
|
}
|
|
|
|
AppendString(&Out, StrLit("\t"), Scratch.Arena);
|
|
AppendString(&Out, Name, Scratch.Arena);
|
|
AppendString(&Out, StrLit(" = "), Scratch.Arena);
|
|
|
|
// sixten: Output the value of the entry
|
|
if(Entry->Type == Config_Entry_S32 || Entry->Type == Config_Entry_S64)
|
|
{
|
|
s64 IntegerValue;
|
|
if(Entry->Type == Config_Entry_S32)
|
|
{
|
|
IntegerValue = *(s32 *)Entry->Target;
|
|
}
|
|
else
|
|
{
|
|
IntegerValue = *(s64 *)Entry->Target;
|
|
}
|
|
|
|
string Value = ConvertS64ToString(Scratch.Arena, IntegerValue);
|
|
AppendString(&Out, Value, Scratch.Arena);
|
|
}
|
|
else if(Entry->Type == Config_Entry_B32)
|
|
{
|
|
string Value = (*(b32 *)Entry->Target)?StrLit("true"):StrLit("false");
|
|
AppendString(&Out, Value, Scratch.Arena);
|
|
}
|
|
else
|
|
{
|
|
UnimplementedCodepath;
|
|
}
|
|
|
|
AppendString(&Out, StrLit(";\n"), Scratch.Arena);
|
|
}
|
|
|
|
if(!AreEqual(LastDir, MakeString(0, 0)))
|
|
{
|
|
AppendString(&Out, StrLit("}"), Scratch.Arena);
|
|
}
|
|
|
|
string FinalOut = JoinStringList(&Out, Scratch.Arena);
|
|
|
|
platform_file_handle Handle = Platform.OpenFile(Path, PlatformAccess_Write);
|
|
if(Handle.IsValid)
|
|
{
|
|
Platform.WriteFile(Handle, FinalOut.Data, 0, FinalOut.Count);
|
|
Platform.CloseFile(Handle);
|
|
}
|
|
|
|
ReleaseScratch(Scratch);
|
|
} |