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(char *Message, memory_arena *Arena) { string String = PushFormat(Arena, "An error occured during config parsing:\n\"%s\"", Message); Platform.ShowMessage(String, Platform_Message_Warning); } static void Config_ReadFile(config *Config, string Path) { temporary_memory Scratch = GetScratch(); tokenizer Tokenizer = Tokenizer_BeginTokenization(Platform_ReadEntireFile(Scratch.Arena, Path)); if(Tokenizer.Input.Data) { token Token; for(;;) { Token = Tokenizer_GetNextToken(&Tokenizer); if(Token.Type == Token_Identifier) { string Dir = Token.String; if(Tokenizer_RequireToken(&Tokenizer, Token_CurlyOpen)) { for(;;) { Token = Tokenizer_GetNextToken(&Tokenizer); if(Token.Type == Token_Identifier) { string Name = Token.String; if(Tokenizer_RequireToken(&Tokenizer, Token_Equals)) { Token = Tokenizer_GetNextToken(&Tokenizer); if(Token.Type == Token_IntegerValue) { s64 Value = ConvertStringToS64(Token.String); string FullName = PushFormat(Scratch.Arena, "%S/%S", Dir, Name); config_entry *Entry = Config_FindEntryByName(Config, FullName); if(Entry) { if(Entry->Type == Config_Entry_S32) { *(s32 *)Entry->Target = (s32)Value; } else if(Entry->Type == Config_Entry_S64) { *(s64 *)Entry->Target = Value; } else { Config_ParseError("Entry has wrong data type.", Scratch.Arena); goto End; } } else { Config_ParseError("Cannot find entry.", Scratch.Arena); goto End; } } else if(Token.Type == Token_Identifier) { string FullName = PushFormat(Scratch.Arena, "%S/%S", Dir, Name); config_entry *Entry = Config_FindEntryByName(Config, FullName); if(Entry) { if(AreEqual(Token.String, StrLit("true"))) { *(b32 *)Entry->Target = true; } else if(AreEqual(Token.String, StrLit("false"))) { *(b32 *)Entry->Target = false; } else { Config_ParseError("Entry has wrong data type.", Scratch.Arena); goto End; } } else { Config_ParseError("Cannot find entry.", Scratch.Arena); goto End; } } else { Config_ParseError("Expected a value.", Scratch.Arena); goto End; } if(!Tokenizer_RequireToken(&Tokenizer, Token_Semicolon)) { Config_ParseError("Expected a ';'.", Scratch.Arena); goto End; } } else { Config_ParseError("Expected '='.", Scratch.Arena); goto End; } } else if(Token.Type == Token_CurlyClose) { break; } else { Config_ParseError("Expected '}' or identifier.", Scratch.Arena); goto End; } } } else { Config_ParseError("Expected '{'.", Scratch.Arena); goto End; } } else if(Token.Type == Token_EndOfFile) { goto End; } else { Config_ParseError("Unexpected token.", Scratch.Arena); goto End; } } } End: ReleaseScratch(Scratch); } 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); }