#include "CodegenConfig.hpp" #include "CodegenDecl.hpp" #include "CodegenLexer.hpp" #include "CodegenModel.hpp" #include "CodegenModelArchive.hpp" #include "CodegenOutput.hpp" #include "CodegenUtils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; namespace fs = std::filesystem; // TODO support codegen target in .cpp files struct AppState { /*nullable*/ CodegenModelArchive* modelArchive = nullptr; robin_hood::unordered_map sourceFiles; std::string_view outputDir; std::string_view databaseFilePath; SourceFile& GetOrCreateSourceFile(std::string_view filename) { auto iter = sourceFiles.find(filename); if (iter != sourceFiles.end()) { return iter->second; } else { auto [iter, success] = sourceFiles.try_emplace(std::string(filename), SourceFile{}); auto& filename = iter->first; // NOTE: shadows the parameter `filename` auto& sourceFile = iter->second; sourceFile.filename = filename; return sourceFile; } } }; FSTR_LUT_DECL(ClexNames, CLEX_eof, CLEX_ext_COUNT) { FSTR_LUT_MAP_FOR(ClexNames); FSTR_LUT_MAP_ENUM(CLEX_intlit); FSTR_LUT_MAP_ENUM(CLEX_floatlit); FSTR_LUT_MAP_ENUM(CLEX_id); FSTR_LUT_MAP_ENUM(CLEX_dqstring); FSTR_LUT_MAP_ENUM(CLEX_sqstring); FSTR_LUT_MAP_ENUM(CLEX_charlit); FSTR_LUT_MAP_ENUM(CLEX_eq); FSTR_LUT_MAP_ENUM(CLEX_noteq); FSTR_LUT_MAP_ENUM(CLEX_lesseq); FSTR_LUT_MAP_ENUM(CLEX_greatereq); FSTR_LUT_MAP_ENUM(CLEX_andand); FSTR_LUT_MAP_ENUM(CLEX_oror); FSTR_LUT_MAP_ENUM(CLEX_shl); FSTR_LUT_MAP_ENUM(CLEX_shr); FSTR_LUT_MAP_ENUM(CLEX_plusplus); FSTR_LUT_MAP_ENUM(CLEX_minusminus); FSTR_LUT_MAP_ENUM(CLEX_pluseq); FSTR_LUT_MAP_ENUM(CLEX_minuseq); FSTR_LUT_MAP_ENUM(CLEX_muleq); FSTR_LUT_MAP_ENUM(CLEX_diveq); FSTR_LUT_MAP_ENUM(CLEX_modeq); FSTR_LUT_MAP_ENUM(CLEX_andeq); FSTR_LUT_MAP_ENUM(CLEX_oreq); FSTR_LUT_MAP_ENUM(CLEX_xoreq); FSTR_LUT_MAP_ENUM(CLEX_arrow); FSTR_LUT_MAP_ENUM(CLEX_eqarrow); FSTR_LUT_MAP_ENUM(CLEX_shleq); FSTR_LUT_MAP_ENUM(CLEX_shreq); FSTR_LUT_MAP_ENUM(CLEX_ext_single_char); FSTR_LUT_MAP_ENUM(CLEX_ext_double_colon); FSTR_LUT_MAP_ENUM(CLEX_ext_dot_dot_dot); } FSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { FSTR_LUT_MAP_FOR(EnumUnderlyingType); FSTR_LUT_MAP(EUT_Int8, "int8_t"); FSTR_LUT_MAP(EUT_Int16, "int16_t"); FSTR_LUT_MAP(EUT_Int32, "int32_t"); FSTR_LUT_MAP(EUT_Int64, "int64_t"); FSTR_LUT_MAP(EUT_Uint8, "uint8_t"); FSTR_LUT_MAP(EUT_Uint16, "uint16_t"); FSTR_LUT_MAP(EUT_Uint32, "uint32_t"); FSTR_LUT_MAP(EUT_Uint64, "uint64_t"); } RSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { RSTR_LUT_MAP_FOR(EnumUnderlyingType); // Platform-dependent types RSTR_LUT_MAP(EUT_Int16, "short"); RSTR_LUT_MAP(EUT_Int16, "short int"); RSTR_LUT_MAP(EUT_Uint16, "unsigned short"); RSTR_LUT_MAP(EUT_Uint16, "unsigned short int"); RSTR_LUT_MAP(EUT_Int32, "int"); RSTR_LUT_MAP(EUT_Uint32, "unsigned"); RSTR_LUT_MAP(EUT_Uint32, "unsigned int"); #ifdef _WIN32 RSTR_LUT_MAP(EUT_Int32, "long"); RSTR_LUT_MAP(EUT_Int32, "long int"); RSTR_LUT_MAP(EUT_Uint32, "unsigned long"); RSTR_LUT_MAP(EUT_Uint32, "unsigned long int"); #else RSTR_LUT_MAP(EUT_Int64, "long"); RSTR_LUT_MAP(EUT_Int64, "long int"); RSTR_LUT_MAP(EUT_Uint64, "unsigned long"); RSTR_LUT_MAP(EUT_Uint64, "unsigned long int"); #endif RSTR_LUT_MAP(EUT_Int64, "long long"); RSTR_LUT_MAP(EUT_Int64, "long long int"); RSTR_LUT_MAP(EUT_Uint64, "unsigned long long"); RSTR_LUT_MAP(EUT_Uint64, "unsigned long long int"); // Sized types RSTR_LUT_MAP(EUT_Int8, "int8_t"); RSTR_LUT_MAP(EUT_Int16, "int16_t"); RSTR_LUT_MAP(EUT_Int32, "int32_t"); RSTR_LUT_MAP(EUT_Int64, "int64_t"); RSTR_LUT_MAP(EUT_Uint8, "uint8_t"); RSTR_LUT_MAP(EUT_Uint16, "uint16_t"); RSTR_LUT_MAP(EUT_Uint32, "uint32_t"); RSTR_LUT_MAP(EUT_Uint64, "uint64_t"); } FSTR_LUT_DECL(EnumValuePattern, 0, EVP_COUNT) { FSTR_LUT_MAP_FOR(EnumValuePattern); FSTR_LUT_MAP_ENUM(EVP_Continuous); FSTR_LUT_MAP_ENUM(EVP_Bits); FSTR_LUT_MAP_ENUM(EVP_Random); } enum CppKeyword { CKw_Namespace, CKw_Struct, CKw_Class, CKw_Enum, CKw_Public, CKw_Protected, CKw_Private, CKw_Virtual, CKw_COUNT, }; RSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { RSTR_LUT_MAP_FOR(CppKeyword); RSTR_LUT_MAP(CKw_Namespace, "namespace"); RSTR_LUT_MAP(CKw_Struct, "struct"); RSTR_LUT_MAP(CKw_Class, "class"); RSTR_LUT_MAP(CKw_Enum, "enum"); RSTR_LUT_MAP(CKw_Public, "public"); RSTR_LUT_MAP(CKw_Protected, "protected"); RSTR_LUT_MAP(CKw_Private, "private"); RSTR_LUT_MAP(CKw_Virtual, "virtual"); } enum CodegenDirective { CD_Class, CD_ClassProperty, CD_ClassMethod, CD_Enum, CD_COUNT, }; RSTR_LUT_DECL(CodegenDirective, 0, CD_COUNT) { RSTR_LUT_MAP_FOR(CodegenDirective); RSTR_LUT_MAP(CD_Class, "BRUSSEL_CLASS"); RSTR_LUT_MAP(CD_ClassProperty, "BRUSSEL_PROPERTY"); RSTR_LUT_MAP(CD_ClassMethod, "BRUSSEL_METHOD"); RSTR_LUT_MAP(CD_Enum, "BRUSSEL_ENUM"); } std::vector> TryConsumeDirectiveArgumentList(CodegenLexer& lexer) { std::vector> result; decltype(result)::value_type currentArg; size_t i = lexer.idx; int parenDepth = 0; for (; i < lexer.tokens.size(); ++i) { auto& token = lexer.tokens[i]; if (token.text[0] == '(') { if (parenDepth > 0) { currentArg.push_back(&token); } ++parenDepth; } else if (token.text[0] == ')') { --parenDepth; if (parenDepth == 0) { // End of argument list ++i; // Consume the ')' token break; } } else if (parenDepth > 0) { // Parse these only if we are inside the argument list if (token.text[0] == ',') { result.push_back(std::move(currentArg)); currentArg = {}; } else { currentArg.push_back(&token); } } } if (!currentArg.empty()) { result.push_back(std::move(currentArg)); } lexer.idx = i; return result; } std::vector* GetDirectiveArgument(std::vector>& list, size_t idx, const char* errMsg = nullptr) { if (idx < list.size()) { if (errMsg) { printf("%s", errMsg); } return &list[idx]; } return nullptr; } bool TryConsumeKeyword(CodegenLexer& lexer, CppKeyword keyword) { auto& token = lexer.Current(); if (token.type == CLEX_id) { auto iter = RSTR_LUT(CppKeyword).find(token.text); if (iter != RSTR_LUT(CppKeyword).end()) { ++lexer.idx; return true; } } return false; } bool TryConsumeAnyKeyword(CodegenLexer& lexer) { auto& token = lexer.Current(); if (token.type == CLEX_id && RSTR_LUT(CppKeyword).contains(token.text)) { ++lexer.idx; return true; } return false; } std::optional TryConsumeMemberVariable(CodegenLexer& lexer) { // The identifier/name will always be one single token, right before the 1st '=' (if has initializer) or ';' (no initializer) // NOTE: we assume there is no (a == b) stuff in the templates auto& tokens = lexer.tokens; auto& idx = lexer.idx; size_t idenTokIdx; size_t typeStart = idx; size_t typeEnd; for (; idx < tokens.size(); ++idx) { auto& token = tokens[idx]; if (token.type == CLEX_ext_single_char) { if (token.text[0] == '=') { typeEnd = idx - 1; idenTokIdx = idx - 1; lexer.SkipUntilTokenSingleChar(';'); goto found; } else if (token.text[0] == ';') { typeEnd = idx - 1; idenTokIdx = idx - 1; goto found; } } } // We reached end of input but still no end of statement return {}; found: if (tokens[idenTokIdx].type != CLEX_id) { // Expected identifier, found something else return {}; } DeclMemberVariable result; result.name = tokens[idenTokIdx].text; result.type = CombineTokens(std::span(&tokens[typeStart], &tokens[typeEnd])); // Consume the '=' or ';' token ++idx; return result; } enum StructMetaGenOptions { SMGO_InheritanceHiearchy, SMGO_COUNT, }; RSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { RSTR_LUT_MAP_FOR(StructMetaGenOptions); RSTR_LUT_MAP(SMGO_InheritanceHiearchy, "InheritanceHiearchy"); } enum StructPropertyOptions { SPO_Getter, SPO_Setter, SPO_COUNT, }; RSTR_LUT_DECL(StructPropertyOptions, 0, SPO_COUNT) { RSTR_LUT_MAP_FOR(StructPropertyOptions); RSTR_LUT_MAP(SPO_Getter, "GETTER"); RSTR_LUT_MAP(SPO_Setter, "SETTER"); } enum EnumMetaGenOptions { EMGO_ToString, EMGO_FromString, EMGO_ExcludeUseHeuristics, EMGO_RemovePrefix, EMGO_AddPrefix, EMGO_COUNT, }; RSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { RSTR_LUT_MAP_FOR(EnumMetaGenOptions); RSTR_LUT_MAP(EMGO_ToString, "ToString"); RSTR_LUT_MAP(EMGO_FromString, "FromString"); RSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); RSTR_LUT_MAP(EMGO_RemovePrefix, "RemovePrefix"); RSTR_LUT_MAP(EMGO_AddPrefix, "AddPrefix"); } void GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, const char* arrayName, const std::vector& filteredElements) { CodegenOutputThing thing; APPEND_FMT_LN(thing.text, "const char* %s[] = {", arrayName); for (auto& elm : filteredElements) { APPEND_FMT_LN(thing.text, "\"%s\",", elm.name.c_str()); } APPEND_LIT_LN(thing.text, "};"); out.AddOutputThing(std::move(thing)); } void GenerateEnumStringMap(CodegenOutput& out, const DeclEnum& decl, const char* mapName, const std::vector& filteredElements) { CodegenOutputThing thing; // TODO out.AddOutputThing(std::move(thing)); } void GenerateForEnum(CodegenOutput& headerOut, CodegenOutput& sourceOut, const DeclEnum& decl) { char enumName[2048]; if (decl.container) { snprintf(enumName, sizeof(enumName), "%.*s::%s", PRINTF_STRING_VIEW(decl.container->fullname), decl.name.c_str()); } else { strncpy(enumName, decl.name.c_str(), sizeof(enumName)); } // TODO mangle to prevent name conflicts of enum in different namespaces auto& declIdName = decl.name; auto useExcludeHeuristics = decl.generateExcludeUseHeuristics; auto filteredElements = [&]() { if (useExcludeHeuristics) { decltype(decl.elements) result; for (auto& elm : decl.elements) { if (elm.name.ends_with("COUNT")) continue; std::string_view trimmedName = elm.name; if (!decl.generateRemovingPrefix.empty() && elm.name.starts_with(decl.generateRemovingPrefix)) { trimmedName = trimmedName.substr(decl.generateRemovingPrefix.size()); } result.push_back(DeclEnumElement{ .name = decl.generatingAddingPrefix + std::string(trimmedName), .value = elm.value, }); } return result; } else { return decl.elements; } }(); if (decl.generateToString) { // Generate value -> string lookup table and function INPLACE_FMT(val2StrName, "gCG_%s_Val2Str", declIdName.c_str()); switch (decl.GetPattern()) { case EVP_Continuous: { GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); int minVal = filteredElements.empty() ? 0 : filteredElements.front().value; int maxVal = filteredElements.empty() ? 0 : filteredElements.back().value; CodegenOutputThing lookupFunctionDecl; { auto& o = lookupFunctionDecl.text; APPEND_LIT_LN(o, "template <>"); APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value);", enumName, enumName); } CodegenOutputThing lookupFunctionDef; { auto& o = lookupFunctionDef.text; APPEND_LIT_LN(o, "template <>"); APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value) {", enumName, enumName); APPEND_FMT_LN(o, " auto intVal = (%s)value;", FSTR_LUT_LOOKUP(EnumUnderlyingType, decl.underlyingType)); APPEND_FMT_LN(o, " if (intVal < %d || intVal > %d) return {};", minVal, maxVal); APPEND_FMT_LN(o, " return %s[intVal - %d];", val2StrName, minVal); APPEND_LIT_LN(o, "}"); } headerOut.AddOutputThing(std::move(lookupFunctionDecl)); sourceOut.AddOutputThing(std::move(lookupFunctionDef)); } break; case EVP_Bits: { GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); // TODO } break; case EVP_Random: { GenerateEnumStringMap(sourceOut, decl, val2StrName, filteredElements); // TODO } break; case EVP_COUNT: break; } } if (decl.generateFromString) { // Generate string -> value lookup table INPLACE_FMT(str2ValName, "gCG_%s_Str2Val", declIdName.c_str()); CodegenOutputThing lookupTable; { auto& o = lookupTable.text; // TODO use correct underlying type APPEND_FMT_LN(o, "constinit frozen::unordered_map %s = {", filteredElements.size(), str2ValName); for (auto& elm : filteredElements) { APPEND_FMT_LN(o, "{\"%s\", %" PRId64 "},", elm.name.c_str(), elm.value); } APPEND_LIT_LN(o, "};"); } // Generate lookup function CodegenOutputThing lookupFunctionDecl; { auto& o = lookupFunctionDecl.text; APPEND_LIT_LN(o, "template <>"); APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value);", enumName, enumName); } CodegenOutputThing lookupFunctionDef; { auto& o = lookupFunctionDef.text; APPEND_LIT_LN(o, "template <>"); APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value) {", enumName, enumName); APPEND_FMT_LN(o, " auto iter = %s.find(value);", str2ValName); APPEND_FMT_LN(o, " if (iter != %s.end()) {", str2ValName); APPEND_FMT_LN(o, " return (%s)iter->second;", enumName); APPEND_LIT_LN(o, " } else {"); APPEND_LIT_LN(o, " return {};"); APPEND_LIT_LN(o, " }"); APPEND_LIT_LN(o, "}"); } sourceOut.AddOutputThing(std::move(lookupTable)); headerOut.AddOutputThing(std::move(lookupFunctionDecl)); sourceOut.AddOutputThing(std::move(lookupFunctionDef)); } } void GenerateClassProperty(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { // TODO } void GenerateClassFunction(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { // TODO } void GenerateForClassMetadata( CodegenOutput& headerOutput, CodegenOutput& sourceOutput, const DeclStruct& decl) // { // TODO mangle auto declIdName = decl.name.c_str(); CodegenOutputThing data; // TODO generate type id, this needs global scanning APPEND_FMT_LN(data.text, "const TypeInfo* const gCGtype_%s_BaseClasses[] = {", declIdName); for (auto& baseClass : decl.baseClasses) { // TODO get ptr to TypeInfo, this needs global scanning for non-file local classes } APPEND_LIT_LN(data.text, "};"); APPEND_FMT_LN(data.text, "const TypePropertyInfo gCGtype_%s_Properties[] = {", declIdName); for (auto& property : decl.memberVariables) { APPEND_FMT_LN(data.text, "{.name=\"%s\"sv, .getterName=\"%s\"sv, .setterName=\"%s\"sv},", property.name.c_str(), property.getterName.c_str(), property.setterName.c_str()); } APPEND_LIT_LN(data.text, "};"); APPEND_FMT_LN(data.text, "const TypeInfo gCGtype_%s_TypeInfo = {", declIdName); APPEND_FMT_LN(data.text, ".name = \"%s\"sv,", declIdName); APPEND_FMT_LN(data.text, ".parents = gCGtype_%s_BaseClasses,", declIdName); APPEND_FMT_LN(data.text, ".properties = gCGtype_%s_Properties};", declIdName); CodegenOutputThing queryFunc; APPEND_FMT(queryFunc.text, "template <>\n" "const TypeInfo* Metadata::GetTypeInfo<%.*s>() {\n" " return &gCGtype_%s_TypeInfo;\n" "}\n", PRINTF_STRING_VIEW(decl.fullname), declIdName); sourceOutput.AddOutputThing(std::move(data)); sourceOutput.AddOutputThing(std::move(queryFunc)); } struct NamespaceStackframe { // The current namespace that owns the brace level, see example DeclNamespace* ns = nullptr; // Brace depth `ns` was created at (e.g. [std::details].depth == 0) int depth = 0; }; struct ParserState { // TODO }; struct ParserOutput { CodegenModel model; CodegenOutput headerOutput; CodegenOutput sourceOutput; CodegenOutput standaloneSourceOutput; // Example: // namespace std::details { // /* [stack top].ns = std::details */ // /* [stack top].depth = std */ // } // namespace foo { // /* [stack top].ns = foo */ // /* [stack top].depth = foo */ // namespace details { // /* [stack top].ns = foo::details */ // /* [stack top].depth = foo::details */ // } // } std::vector nsStack; // The current effective namespace, see example DeclNamespace* currentNamespace = nullptr; DeclStruct* currentStruct = nullptr; DeclEnum* currentEnum = nullptr; int currentBraceDepth = 0; int currentStructBraceDepth = -1; int currentEnumBraceDepth = -1; }; void HandleDirectiveEnum(AppState& as, ParserOutput& ps, CodegenLexer& lexer) { // Consume the directive ++lexer.idx; if (!ps.currentEnum) { printf("[ERROR] BRUSSEL_ENUM must be used within a enum\n"); return; } auto argList = TryConsumeDirectiveArgumentList(lexer); auto& lut = RSTR_LUT(EnumMetaGenOptions); for (auto& arg : argList) { if (arg.empty()) { printf("[ERROR] empty argument is invalid in BRUSSEL_ENUM\n"); continue; } auto& optionDirective = arg[0]->text; auto iter = lut.find(optionDirective); if (iter == lut.end()) { printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionDirective.c_str()); } auto option = iter->second; switch (option) { case EMGO_ToString: ps.currentEnum->generateToString = true; break; case EMGO_FromString: ps.currentEnum->generateFromString = true; break; case EMGO_ExcludeUseHeuristics: ps.currentEnum->generateExcludeUseHeuristics = true; break; case EMGO_RemovePrefix: { if (argList.size() <= 1) { printf("[ERROR] missing argument for RemovePrefix"); break; } ps.currentEnum->generateRemovingPrefix = arg[1]->text; } break; case EMGO_AddPrefix: { if (argList.size() <= 1) { printf("[ERROR] missing argument for AddPrefix"); break; } ps.currentEnum->generatingAddingPrefix = arg[1]->text; } break; case EMGO_COUNT: break; } } ps.currentEnum->generating = true; } CodegenLexer LexInputFile(AppState& as, std::string_view source) { CodegenLexer result; result.InitializeFrom(source); return result; } void ParseInputFileAndGenerate(AppState& as, CodegenLexer& /*lexingState*/ ls, std::string_view filenameStem) { #if CODEGEN_DEBUG_PRINT printf("BEGIN tokens\n"); for (auto& token : lexer.tokens) { switch (token.type) { case CLEX_intlit: { printf(" token %-32s = %ld\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerIntNumber); } break; case CLEX_floatlit: { printf(" token %-32s = %f\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerRealNumber); } break; default: { printf(" token %-32s '%s'\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.text.c_str()); } break; } } printf("END tokens\n"); #endif auto& sourceFile = as.GetOrCreateSourceFile(filenameStem); sourceFile.header = true; sourceFile.reprocessing = true; // TODO move lexedTokens and consumption related functions to ParserState struct ParserOutput po; { INPLACE_FMT(hpp, "%.*s.gh.inl", PRINTF_STRING_VIEW(filenameStem)); INPLACE_FMT(cpp, "%.*s.gs.inl", PRINTF_STRING_VIEW(filenameStem)); Utils::ProduceGeneratedHeader(hpp, po.headerOutput, cpp, po.sourceOutput); } auto& tokens = ls.tokens; auto& idx = ls.idx; while (ls.idx < ls.tokens.size()) { auto& token = ls.Current(); bool incrementTokenIdx = true; // Reamalgamate token type and single char tokens int tokenKey; if (token.type == CLEX_ext_single_char) { tokenKey = token.text[0]; } else { tokenKey = token.type; } switch (tokenKey) { case CLEX_id: { CppKeyword keyword; { auto& map = RSTR_LUT(CppKeyword); auto iter = map.find(token.text); if (iter != map.end()) { keyword = iter->second; } else { keyword = CKw_COUNT; // Skip keyword section } } switch (keyword) { case CKw_Namespace: { ++idx; incrementTokenIdx = false; int nestingCount = 0; while (true) { if (tokens[idx].type != CLEX_id) { // TODO better error recovery // TODO handle annoymous namespaces printf("[ERROR] invalid syntax for namespace\n"); break; } po.currentNamespace = po.model.AddNamespace(DeclNamespace{ .container = po.currentNamespace, .name = tokens[idx].text, }); // Consume the identifier token ++idx; if (tokens[idx].type == CLEX_ext_double_colon) { // Consume the "::" token ++idx; } else { break; } } po.nsStack.push_back(NamespaceStackframe{ .ns = po.currentNamespace, .depth = po.currentBraceDepth, }); goto endCaseCLEX_id; } case CKw_Struct: case CKw_Class: { // Consume the 'class' or 'struct' keyword ++idx; incrementTokenIdx = false; auto& idenTok = tokens[idx]; if (idenTok.type != CLEX_id) { printf("[ERROR] invalid syntax for struct or class\n"); break; } DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); auto& name = idenTok.text; auto fullname = Utils::MakeFullName(name, po.currentNamespace); DeclStruct structDecl; structDecl.sourceFile = &sourceFile; structDecl.container = po.currentNamespace; structDecl.name = name; // Consume the identifier token ++idx; if (ls.TryConsumeSingleCharToken(':')) { while (true) { // Public, protected, etc. TryConsumeAnyKeyword(ls); auto& idenTok = tokens[idx]; if (idenTok.type != CLEX_id) { printf("[ERROR] invalid syntax for class inheritance list\n"); goto endCase; } // TODO support namespace qualified names auto baseClassFullname = Utils::MakeFullName(idenTok.text, po.currentNamespace); auto baseClassDecl = po.model.FindStruct(baseClassFullname); if (baseClassDecl) { // We silently ignore a non-existent base class, because they may reside in a file that we didn't scan structDecl.baseClasses.push_back(baseClassDecl); } // Consume the identifier token ++idx; if (ls.TryConsumeSingleCharToken('{')) { // End of base class list --idx; // Give the '{' token back to the main loop break; } else if (!ls.TryConsumeSingleCharToken(',')) { // If the list didn't end, we expect a comma (then followed by more entries) printf("[ERROR] invalid syntax for class inheritance list\n"); goto endCase; } // NOTE: we currently only scan one base class to workaround some code inherits from template classes after their initial base class // TODO remove this hack break; } } { // Get a pointer to the decl inside CodegenInput's storage auto decl = po.model.AddStruct(std::move(fullname), std::move(structDecl)); po.currentStruct = decl; po.currentStructBraceDepth = po.currentBraceDepth; } endCase: goto endCaseCLEX_id; } case CKw_Enum: { // Consume the "enum" keyword ++idx; incrementTokenIdx = false; StbLexerToken* idenTok; if (tokens[idx].text == "class") { // Consume the "class" keyword ++idx; idenTok = &tokens[idx]; DEBUG_PRINTF("[DEBUG] found enum class named %s\n", idenTok->text.c_str()); } else { idenTok = &tokens[idx]; DEBUG_PRINTF("[DEBUG] found enum named %s\n", idenTok->text.c_str()); } auto& name = tokens[idx].text; auto fullname = Utils::MakeFullName(name, po.currentNamespace); DeclEnum enumDecl; enumDecl.sourceFile = &sourceFile; enumDecl.container = po.currentNamespace; enumDecl.underlyingType = EUT_Int32; // TODO enumDecl.underlyingTypeStr = "int"; enumDecl.name = name; // Temporarily bind the pointers to local variable, HandleDirectiveEnum() and other functions expect these to the set po.currentEnum = &enumDecl; po.currentEnumBraceDepth = po.currentBraceDepth; // Consume the enum name identifier ++idx; int enumClosingBraceCount = 0; int enumBraceDepth = 0; while (enumClosingBraceCount == 0 && idx < tokens.size()) { auto& token = tokens[idx]; switch (token.type) { case CLEX_id: { if (token.text == "BRUSSEL_ENUM") { // Consume the argument list and skip advancing index: this function already consumed all the tokens about BRUSSEL_ENUM HandleDirectiveEnum(as, po, ls); continue; } else { auto& vec = enumDecl.elements; // Set to the previous enum element's value + 1, or starting from 0 if this is the first // Also overridden in the CLEX_intlit branch auto value = vec.empty() ? 0 : vec.back().value + 1; vec.push_back(DeclEnumElement{ .name = token.text, .value = value, }); } } break; case CLEX_intlit: { auto& vec = enumDecl.elements; if (!vec.empty()) { auto& lastElm = vec.back(); lastElm.value = token.lexerIntNumber; } } break; case CLEX_ext_single_char: { switch (token.text[0]) { case '{': { ++enumBraceDepth; } break; case '}': { --enumBraceDepth; ++enumClosingBraceCount; } break; } } break; } ++idx; } if (po.currentEnum->generating) { GenerateForEnum(po.headerOutput, po.sourceOutput, *po.currentEnum); } { auto decl = po.model.AddEnum(std::move(fullname), std::move(enumDecl)); po.currentEnum = decl; po.currentEnumBraceDepth = po.currentBraceDepth; } // NOTE: we parse the whole enum at once (above code), the enum ends right here after the closing brace '}' po.currentEnum = nullptr; po.currentEnumBraceDepth = -1; goto endCaseCLEX_id; } // We don't care about these keywords case CKw_Public: case CKw_Protected: case CKw_Private: case CKw_Virtual: case CKw_COUNT: break; } CodegenDirective directive; { auto& map = RSTR_LUT(CodegenDirective); auto iter = map.find(token.text); if (iter != map.end()) { directive = iter->second; } else { directive = CD_COUNT; // Skip directive section } } switch (directive) { case CD_Class: { // Consume the directive ++idx; incrementTokenIdx = false; if (!po.currentStruct) { printf("[ERROR] BRUSSEL_CLASS must be used within a class or struct\n"); break; } // Always-on option po.currentStruct->generating = true; auto argList = TryConsumeDirectiveArgumentList(ls); auto& lut = RSTR_LUT(StructMetaGenOptions); for (auto& arg : argList) { if (arg.empty()) { printf("[ERROR] empty argument is invalid in BRUSSEL_CLASS\n"); continue; } auto& optionDirective = arg[0]->text; auto iter = lut.find(optionDirective); if (iter == lut.end()) continue; switch (iter->second) { case SMGO_InheritanceHiearchy: po.currentStruct->generatingInheritanceHiearchy = true; break; case SMGO_COUNT: break; } } goto endCaseCLEX_id; } case CD_ClassProperty: { // Consume the directive ++idx; incrementTokenIdx = false; if (!po.currentStruct || !po.currentStruct->generating) { printf("[ERROR] BRUSSEL_PROPERTY must be used within a class or struct, that has the BRUSSEL_CLASS directive\n"); break; } auto argList = TryConsumeDirectiveArgumentList(ls); auto declOpt = TryConsumeMemberVariable(ls); if (!declOpt.has_value()) { printf("[ERROR] a member variable must immediately follow a BRUSSEL_PROPERTY\n"); break; } auto& decl = declOpt.value(); // Different option's common logic std::string pascalCaseName; auto GetPascalCasedName = [&]() -> const std::string& { if (pascalCaseName.empty()) { pascalCaseName = Utils::MakePascalCase(decl.name); } return pascalCaseName; }; auto& lut = RSTR_LUT(StructPropertyOptions); for (auto& arg : argList) { if (arg.empty()) { printf("[ERROR] empty argument is invalid in BRUSSEL_PROPERTY\n"); continue; } auto& optionDirective = arg[0]->text; auto iter = lut.find(optionDirective); if (iter == lut.end()) continue; switch (iter->second) { case SPO_Getter: { // TODO I'm too lazy to write error checks, just let the codegen crash auto& getterName = arg.at(1)->text; if (getterName == "auto") { // NOTE: intentionally shadowing INPLACE_FMT(getterName, "Get%s", GetPascalCasedName().c_str()); // TODO generate getter function decl.getterName = getterName; } else { decl.getterName = getterName; } } break; case SPO_Setter: { // TODO auto& setterName = arg.at(1)->text; if (setterName == "auto") { // NOTE: intentionally shadowing INPLACE_FMT(setterName, "Set%s", GetPascalCasedName().c_str()); // TODO generate setter function decl.setterName = setterName; } else { decl.setterName = setterName; } } break; case SPO_COUNT: break; } } po.currentStruct->memberVariables.push_back(std::move(decl)); goto endCaseCLEX_id; } case CD_ClassMethod: { // Consume the directive ++idx; incrementTokenIdx = false; goto endCaseCLEX_id; } // This directive always appear inside a enum{} block, which is handled above in the keywords section case CD_Enum: case CD_COUNT: break; } endCaseCLEX_id:; } break; case '{': { po.currentBraceDepth++; if (po.currentBraceDepth < 0) { printf("[WARNING] unbalanced brace\n"); } } break; case '}': { po.currentBraceDepth--; if (po.currentBraceDepth < 0) { printf("[WARNING] unbalanced brace\n"); } if (!po.nsStack.empty()) { auto& ns = po.nsStack.back(); if (ns.depth == po.currentBraceDepth) { po.nsStack.pop_back(); if (!po.nsStack.empty()) { po.currentNamespace = po.nsStack.back().ns; } else { po.currentNamespace = nullptr; } } } if (po.currentStruct && po.currentBraceDepth == po.currentStructBraceDepth) { // Exit struct if (po.currentStruct->generating) { GenerateForClassMetadata(po.headerOutput, po.sourceOutput, *po.currentStruct); } if (po.currentStruct->generatingInheritanceHiearchy) { // NOTE: this option is transitive to all child classes (as long as they have the basic annotation) // TODO } po.currentStruct = nullptr; po.currentStructBraceDepth = -1; } if (po.currentEnum && po.currentBraceDepth == po.currentEnumBraceDepth) { // Exit enum po.currentEnum = nullptr; po.currentEnumBraceDepth = -1; } } break; } if (incrementTokenIdx) { ++idx; } } if (po.currentBraceDepth != 0) { printf("[WARNING] unbalanced brace at end of file\n"); } INPLACE_FMT(generatedHeaderInlName, "%.*s/%.*s.gh.inl", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); Utils::WriteOutputFile(po.headerOutput, generatedHeaderInlName); INPLACE_FMT(generatedSourceInlName, "%.*s/%.*s.gs.inl", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); Utils::WriteOutputFile(po.sourceOutput, generatedSourceInlName); INPLACE_FMT(generatedCppName, "%.*s/%.*s.g.cpp", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); Utils::WriteOutputFile(po.standaloneSourceOutput, generatedCppName); if (as.modelArchive) { as.modelArchive->DeleteDeclsRelatedToFile(filenameStem); as.modelArchive->Store(po.model); } } void HandleInputFile(AppState& as, const fs::path& path) { auto filenameStem = path.stem().string(); auto lexingState = LexInputFile(as, Utils::ReadFileAsString(path)); ParseInputFileAndGenerate(as, lexingState, filenameStem); } enum InputOpcode { IOP_ProcessSingleFile, IOP_ProcessRecursively, IOP_ProcessFileList, IOP_COUNT, }; void HandleArgument(AppState& as, InputOpcode opcode, std::string_view operand) { switch (opcode) { case IOP_ProcessSingleFile: { DEBUG_PRINTF("Processing single file %.*s\n", PRINTF_STRING_VIEW(operand)); HandleInputFile(as, fs::path(operand)); } break; case IOP_ProcessRecursively: { DEBUG_PRINTF("Recursively processing folder %.*s\n", PRINTF_STRING_VIEW(operand)); fs::path startPath(operand); for (auto& item : fs::recursive_directory_iterator(startPath)) { if (!item.is_regular_file()) { continue; } auto& path = item.path(); if (auto pathExt = path.extension(); pathExt != ".h" && pathExt != ".hpp") { continue; } DEBUG_PRINTF("Processing subfile %s\n", path.string().c_str()); HandleInputFile(as, path); } } break; case IOP_ProcessFileList: { DEBUG_PRINTF("Processing file list %.*s\n", PRINTF_STRING_VIEW(operand)); fs::path fileListPath(operand); auto fileList = Utils::OpenCstdioFile(fileListPath, Utils::Read); if (!fileList) { // NOTE: need this because our dirty-file-list generation algorithm in CMakeLists.txt doesn't produce a file when nothing is changed DEBUG_PRINTF("File-list file does not exist, silently skipping.\n"); break; } DEFER { fclose(fileList); }; std::string line; while (Utils::ReadCstdioLine(fileList, line)) { // Remove '\n' line.pop_back(); DEBUG_PRINTF("Processing file in list %.*s\n", line.c_str()); HandleInputFile(as, fs::path(line)); } } break; case IOP_COUNT: break; } } InputOpcode ParseInputOpcode(std::string_view text) { if (text == "single"sv) { return IOP_ProcessSingleFile; } else if (text == "rec"sv) { return IOP_ProcessRecursively; } else if (text == "fileList"sv) { return IOP_ProcessFileList; } else { INPLACE_FMT(msg, "Unknown input opcode %s\n", text.data()); throw std::runtime_error(msg); } } int main(int argc, char* argv[]) { FSTR_LUT_INIT(ClexNames); FSTR_LUT_INIT(EnumUnderlyingType); RSTR_LUT_INIT(EnumUnderlyingType); FSTR_LUT_INIT(EnumValuePattern); RSTR_LUT_INIT(CppKeyword); RSTR_LUT_INIT(CodegenDirective); RSTR_LUT_INIT(StructMetaGenOptions); RSTR_LUT_INIT(StructPropertyOptions); RSTR_LUT_INIT(EnumMetaGenOptions); // TODO better arg parser // option 1: use cxxopts and positional arguments // option 2: take one argument only, being a json objecet AppState as; // If no cli is provided (argv[0] conventionally but not mandatorily the cli), this will do thing // Otherwise, start with the 2nd element in the array, which is the 1st actual argument if (argc <= 1) { // NOTE: keep in sync with various enum options and parser code printf(&R"""( USAGE: codegen.exe --output-dir= [--database=] [:]... where --output-dir=: the *directory* to write generated contents to. This will NOT automatically create the directory. --database=: the *file* to use for the code model database. is one of: "single" process this file only "rec" starting at the given directory , recursively process all .h .hpp files "fileList" read as a text file, and process each line as a separate file path )"""[1]); return -1; } // Named argument pass robin_hood::unordered_map namedArguments{ { "output-dir"sv, &as.outputDir }, { "database"sv, &as.databaseFilePath }, }; for (int i = 1; i < argc; ++i) { std::string_view arg(argv[i]); if (!arg.starts_with("--")) { // Convention: a "--" argument indicates everything afterwords are positional arguments if (arg.size() == 2) { break; } else { continue; } } size_t equalLoc = arg.find('='); auto oper = arg.substr(/*--*/ 2, equalLoc - 2); auto iter = namedArguments.find(oper); if (iter != namedArguments.end()) { auto storage = iter->second; if (storage) { if (equalLoc == std::string_view::npos) { *storage = ""sv; } else { *storage = arg.substr(equalLoc + 1); } } } } DEBUG_PRINTF("Outputting to directory %.*s.\n", PRINTF_STRING_VIEW(as.outputDir)); DEBUG_PRINTF("Databse file: %.*s.\n", PRINTF_STRING_VIEW(as.databaseFilePath)); CodegenModelArchive archive(as.databaseFilePath); as.modelArchive = &archive; // Positional argument pass for (int i = 1; i < argc; ++i) { std::string_view arg(argv[i]); if (arg.starts_with("--")) { continue; } DEBUG_PRINTF("Processing input command %s\n", argv[i]); auto separatorLoc = arg.find(':'); if (separatorLoc != std::string_view::npos) { auto opcodeString = arg.substr(0, separatorLoc); auto opcode = ParseInputOpcode(opcodeString); auto operand = arg.substr(separatorLoc + 1); HandleArgument(as, opcode, operand); } } return 0; }