vn/code/vn_config.cpp

327 lines
10 KiB
C++

static config *CreateConfig(void)
{
arena *Arena = ArenaAlloc(Kilobytes(4), true);
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, 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, Text, T_IsIrregular);
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->Kind & TokenKind_Identifier)
{
Config_ParseListPush(Scratch.Arena, &FullPath, TokenString);
ParseMode = ConfigParseMode_ScanForCurlyOpenOrEquals;
Token += 1;
goto TokenConsumed;
}
//- sixten: scan for curly close
if(ParseMode == ConfigParseMode_Main && Token->Kind == TokenKind_CurlyClose)
{
Config_ParseListPop(&FullPath);
Token += 1;
goto TokenConsumed;
}
//- sixten: scan for curly open
if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Kind == TokenKind_CurlyOpen)
{
ParseMode = ConfigParseMode_Main;
Token += 1;
goto TokenConsumed;
}
//- sixten: scan for equals
if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Kind & TokenKind_Equal)
{
ParseMode = ConfigParseMode_ScanForValue;
Token += 1;
goto TokenConsumed;
}
//- sixten: scan for semicolon
if(ParseMode == ConfigParseMode_ScanForSemicolon && Token->Kind == TokenKind_Semicolon)
{
ParseMode = ConfigParseMode_Main;
Token += 1;
goto TokenConsumed;
}
//- sixten: scan for boolean value
if(ParseMode == ConfigParseMode_ScanForValue && (Token->Kind == TokenKind_True || Token->Kind == TokenKind_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->Kind & TokenKind_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(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(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, 0LL);
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, 0LL)))
{
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, 0LL)))
{
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);
}