/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ #include #include #include struct meta_struct { char *Name; meta_struct *Next; }; static meta_struct *FirstMetaStruct; static char * ReadEntireFileIntoMemoryAndNullTerminate(char *FileName) { char *Result = 0; FILE *File = fopen(FileName, "r"); if(File) { fseek(File, 0, SEEK_END); size_t FileSize = ftell(File); fseek(File, 0, SEEK_SET); Result = (char *)malloc(FileSize + 1); fread(Result, FileSize, 1, File); Result[FileSize] = 0; fclose(File); } return(Result); } enum token_type { Token_Unknown, Token_OpenParen, Token_CloseParen, Token_Colon, Token_Semicolon, Token_Asterisk, Token_OpenBracket, Token_CloseBracket, Token_OpenBrace, Token_CloseBrace, Token_String, Token_Identifier, Token_EndOfStream, }; struct token { token_type Type; size_t TextLength; char *Text; }; struct tokenizer { char *At; }; inline bool IsEndOfLine(char C) { bool Result = ((C == '\n') || (C == '\r')); return(Result); } inline bool IsWhitespace(char C) { bool Result = ((C == ' ') || (C == '\t') || (C == '\v') || (C == '\f') || IsEndOfLine(C)); return(Result); } inline bool IsAlpha(char C) { bool Result = (((C >= 'a') && (C <= 'z')) || ((C >= 'A') && (C <= 'Z'))); return(Result); } inline bool IsNumber(char C) { bool Result = ((C >= '0') && (C <= '9')); return(Result); } inline bool TokenEquals(token Token, char *Match) { char *At = Match; for(int Index = 0; Index < Token.TextLength; ++Index, ++At) { if((*At == 0) || (Token.Text[Index] != *At)) { return(false); } } bool Result = (*At == 0); return(Result); } static void EatAllWhitespace(tokenizer *Tokenizer) { for(;;) { if(IsWhitespace(Tokenizer->At[0])) { ++Tokenizer->At; } else if((Tokenizer->At[0] == '/') && (Tokenizer->At[1] == '/')) { Tokenizer->At += 2; while(Tokenizer->At[0] && !IsEndOfLine(Tokenizer->At[0])) { ++Tokenizer->At; } } else if((Tokenizer->At[0] == '/') && (Tokenizer->At[1] == '*')) { Tokenizer->At += 2; while(Tokenizer->At[0] && !((Tokenizer->At[0] == '*') && (Tokenizer->At[1] == '/'))) { ++Tokenizer->At; } if(Tokenizer->At[0] == '*') { Tokenizer->At += 2; } } else { break; } } } static token GetToken(tokenizer *Tokenizer) { EatAllWhitespace(Tokenizer); token Token = {}; Token.TextLength = 1; Token.Text = Tokenizer->At; char C = Tokenizer->At[0]; ++Tokenizer->At; switch(C) { case '\0': {Token.Type = Token_EndOfStream;} break; case '(': {Token.Type = Token_OpenParen;} break; case ')': {Token.Type = Token_CloseParen;} break; case ':': {Token.Type = Token_Colon;} break; case ';': {Token.Type = Token_Semicolon;} break; case '*': {Token.Type = Token_Asterisk;} break; case '[': {Token.Type = Token_OpenBracket;} break; case ']': {Token.Type = Token_CloseBracket;} break; case '{': {Token.Type = Token_OpenBrace;} break; case '}': {Token.Type = Token_CloseBrace;} break; case '"': { Token.Type = Token_String; Token.Text = Tokenizer->At; while(Tokenizer->At[0] && Tokenizer->At[0] != '"') { if((Tokenizer->At[0] == '\\') && Tokenizer->At[1]) { ++Tokenizer->At; } ++Tokenizer->At; } Token.TextLength = Tokenizer->At - Token.Text; if(Tokenizer->At[0] == '"') { ++Tokenizer->At; } } break; default: { if(IsAlpha(C)) { Token.Type = Token_Identifier; while(IsAlpha(Tokenizer->At[0]) || IsNumber(Tokenizer->At[0]) || (Tokenizer->At[0] == '_')) { ++Tokenizer->At; } Token.TextLength = Tokenizer->At - Token.Text; } #if 0 else if(IsNumeric(C)) { ParseNumber(); } #endif else { Token.Type = Token_Unknown; } } break; } return(Token); } static bool RequireToken(tokenizer *Tokenizer, token_type DesiredType) { token Token = GetToken(Tokenizer); bool Result = (Token.Type == DesiredType); return(Result); } static void ParseIntrospectionParams(tokenizer *Tokenizer) { for(;;) { token Token = GetToken(Tokenizer); if((Token.Type == Token_CloseParen) || (Token.Type == Token_EndOfStream)) { break; } } } static void ParseMember(tokenizer *Tokenizer, token StructTypeToken, token MemberTypeToken) { #if 1 bool Parsing = true; bool IsPointer = false; while(Parsing) { token Token = GetToken(Tokenizer); switch(Token.Type) { case Token_Asterisk: { IsPointer = true; } break; case Token_Identifier: { printf(" {%s, MetaType_%.*s, \"%.*s\", (u32)&((%.*s *)0)->%.*s},\n", IsPointer ? "MetaMemberFlag_IsPointer" : "0", MemberTypeToken.TextLength, MemberTypeToken.Text, Token.TextLength, Token.Text, StructTypeToken.TextLength, StructTypeToken.Text, Token.TextLength, Token.Text); } break; case Token_Semicolon: case Token_EndOfStream: { Parsing = false; } break; } } #else token Token = GetToken(Tokenizer); switch(Token.Type) { case Token_Asterisk: { ParseMember(Tokenizer, Token); } break; case Token_Identifier: { printf("DEBUG_VALUE(%.*s);\n", Token.TextLength, Token.Text); } break; } #endif } static void ParseStruct(tokenizer *Tokenizer) { token NameToken = GetToken(Tokenizer); if(RequireToken(Tokenizer, Token_OpenBrace)) { printf("member_definition MembersOf_%.*s[] = \n", NameToken.TextLength, NameToken.Text); printf("{\n"); for(;;) { token MemberToken = GetToken(Tokenizer); if(MemberToken.Type == Token_CloseBrace) { break; } else { ParseMember(Tokenizer, NameToken, MemberToken); } } printf("};\n"); meta_struct *Meta = (meta_struct *)malloc(sizeof(meta_struct)); Meta->Name = (char *)malloc(NameToken.TextLength + 1); memcpy(Meta->Name, NameToken.Text, NameToken.TextLength); Meta->Name[NameToken.TextLength] = 0; Meta->Next = FirstMetaStruct; FirstMetaStruct = Meta; } } static void ParseIntrospectable(tokenizer *Tokenizer) { if(RequireToken(Tokenizer, Token_OpenParen)) { ParseIntrospectionParams(Tokenizer); token TypeToken = GetToken(Tokenizer); if(TokenEquals(TypeToken, "struct")) { ParseStruct(Tokenizer); } else { fprintf(stderr, "ERROR: Introspection is only supported for structs right now :(\n"); } } else { fprintf(stderr, "ERROR: Missing parentheses.\n"); } } int main(int ArgCount, char **Args) { char *FileNames[] = { "handmade_sim_region.h", "handmade_platform.h", "handmade_math.h", "handmade_world.h", }; for(int FileIndex = 0; FileIndex < (sizeof(FileNames)/sizeof(FileNames[0])); ++FileIndex) { char *FileContents = ReadEntireFileIntoMemoryAndNullTerminate(FileNames[FileIndex]); tokenizer Tokenizer = {}; Tokenizer.At = FileContents; bool Parsing = true; while(Parsing) { token Token = GetToken(&Tokenizer); switch(Token.Type) { case Token_EndOfStream: { Parsing = false; } break; case Token_Unknown: { } break; case Token_Identifier: { if(TokenEquals(Token, "introspect")) { ParseIntrospectable(&Tokenizer); } } break; default: { // printf("%d: %.*s\n", Token.Type, Token.TextLength, Token.Text); } break; } } } printf("#define META_HANDLE_TYPE_DUMP(MemberPtr, NextIndentLevel) \\\n"); for(meta_struct *Meta = FirstMetaStruct; Meta; Meta = Meta->Next) { printf(" case MetaType_%s: {DEBUGTextLine(Member->Name); DEBUGDumpStruct(ArrayCount(MembersOf_%s), MembersOf_%s, MemberPtr, (NextIndentLevel));} break; %s\n", Meta->Name, Meta->Name, Meta->Name, Meta->Next ? "\\" : ""); } }