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); }