aboutsummaryrefslogtreecommitdiff
path: root/ProjectBrussel/CodegenCompiler/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ProjectBrussel/CodegenCompiler/main.cpp')
-rw-r--r--ProjectBrussel/CodegenCompiler/main.cpp1112
1 files changed, 1112 insertions, 0 deletions
diff --git a/ProjectBrussel/CodegenCompiler/main.cpp b/ProjectBrussel/CodegenCompiler/main.cpp
new file mode 100644
index 0000000..5e052a3
--- /dev/null
+++ b/ProjectBrussel/CodegenCompiler/main.cpp
@@ -0,0 +1,1112 @@
+#include "CodegenConfig.hpp"
+#include "CodegenDecl.hpp"
+#include "CodegenInput.hpp"
+#include "CodegenLexer.hpp"
+#include "CodegenOutput.hpp"
+#include "CodegenUtils.hpp"
+
+#include <Enum.hpp>
+#include <LookupTable.hpp>
+#include <Macros.hpp>
+#include <ScopeGuard.hpp>
+#include <Utils.hpp>
+
+#include <robin_hood.h>
+#include <stb_c_lexer.h>
+#include <cinttypes>
+#include <cstdlib>
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <span>
+#include <string>
+#include <string_view>
+
+using namespace std::literals;
+namespace fs = std::filesystem;
+
+// TODO support codegen target in .cpp files
+
+struct AppState {
+ std::string_view outputDir;
+};
+
+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
+ // TODO all of these can be suffixde with "int"
+ RSTR_LUT_MAP(EUT_Int16, "short");
+ RSTR_LUT_MAP(EUT_Uint16, "unsigned short");
+ 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_Uint32, "unsigned long");
+#else
+ RSTR_LUT_MAP(EUT_Int64, "long");
+ RSTR_LUT_MAP(EUT_Uint64, "unsigned long");
+#endif
+ RSTR_LUT_MAP(EUT_Int64, "long long");
+ RSTR_LUT_MAP(EUT_Uint64, "unsigned long long");
+
+ // 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<std::vector<const StbLexerToken*>>
+TryConsumeDirectiveArgumentList(CodegenLexer& lexer) {
+ std::vector<std::vector<const StbLexerToken*>> 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<const StbLexerToken*>*
+GetDirectiveArgument(std::vector<std::vector<const StbLexerToken*>>& 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<DeclMemberVariable>
+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 {
+ // TODO how tf do we implement this one: needs full source scanning
+ 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_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");
+}
+
+void GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, const char* arrayName, const std::vector<DeclEnumElement>& 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<DeclEnumElement>& filteredElements) {
+ CodegenOutputThing thing;
+ // TODO
+ out.AddOutputThing(std::move(thing));
+}
+
+void GenerateForEnum(CodegenOutput& headerOut, CodegenOutput& sourceOut, const DeclEnum& decl, EnumFlags<EnumMetaGenOptions> options) {
+ 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 = options.IsSet(EMGO_ExcludeUseHeuristics);
+ auto filteredElements = [&]() {
+ if (useExcludeHeuristics) {
+ decltype(decl.elements) result;
+ for (auto& elm : decl.elements) {
+ if (elm.name.ends_with("COUNT")) continue;
+
+ result.push_back(elm);
+ }
+ return result;
+ } else {
+ return decl.elements;
+ }
+ }();
+
+ if (options.IsSet(EMGO_ToString)) {
+ // 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 (options.IsSet(EMGO_FromString)) {
+ // 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<frozen::string, uint64_t, %" PRId64 "> %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));
+}
+
+void HandleInputFile(AppState& state, std::string_view filenameStem, std::string_view source) {
+ CodegenLexer lexer;
+ lexer.InitializeFrom(source);
+
+#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
+
+ CodegenInput cgInput;
+ CodegenOutput cgHeaderOutput;
+ CodegenOutput cgSourceOutput;
+ {
+ INPLACE_FMT(hpp, "%.*s.gh.inl", PRINTF_STRING_VIEW(filenameStem));
+ INPLACE_FMT(cpp, "%.*s.gs.inl", PRINTF_STRING_VIEW(filenameStem));
+ Utils::ProduceGeneratedHeader(hpp, cgHeaderOutput, cpp, cgSourceOutput);
+ }
+ CodegenOutput cgStandaloneSourceOutput;
+
+ int currentBraceDepth = 0;
+ // The current effective namespace, see example
+ DeclNamespace* currentNamespace = nullptr;
+ DeclStruct* currentStruct = nullptr;
+ int currentStructBraceDepth = 0;
+
+ 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;
+ };
+ std::vector<NamespaceStackframe> nsStack;
+
+ // 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 */
+ // }
+ // }
+
+ auto& tokens = lexer.tokens;
+ auto& idx = lexer.idx;
+ while (lexer.idx < lexer.tokens.size()) {
+ auto& token = lexer.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;
+ }
+
+ currentNamespace = cgInput.AddNamespace(DeclNamespace{
+ .container = currentNamespace,
+ .name = tokens[idx].text,
+ });
+
+ // Consume the identifier token
+ ++idx;
+
+ if (tokens[idx].type == CLEX_ext_double_colon) {
+ // Consume the "::" token
+ ++idx;
+ } else {
+ break;
+ }
+ }
+
+ nsStack.push_back(NamespaceStackframe{
+ .ns = currentNamespace,
+ .depth = 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, currentNamespace);
+ DeclStruct structDecl;
+ structDecl.container = currentNamespace;
+ structDecl.name = name;
+
+ // Consume the identifier token
+ ++idx;
+
+ if (lexer.TryConsumeSingleCharToken(':')) {
+ while (true) {
+ // Public, protected, etc.
+ TryConsumeAnyKeyword(lexer);
+
+ 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, currentNamespace);
+ auto baseClassDecl = cgInput.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 (lexer.TryConsumeSingleCharToken('{')) {
+ // End of base class list
+ --idx; // Give the '{' token back to the main loop
+ break;
+ } else if (!lexer.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 = cgInput.AddStruct(std::move(fullname), std::move(structDecl));
+ currentStruct = decl;
+ currentStructBraceDepth = 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());
+ }
+
+ DeclEnum enumDecl;
+ enumDecl.container = currentNamespace;
+ enumDecl.underlyingType = EUT_Int32; // TODO
+ enumDecl.name = tokens[idx].text;
+
+ // 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: {
+ 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;
+ }
+
+ auto fullname = Utils::MakeFullName(enumDecl.name, currentNamespace);
+ cgInput.AddEnum(std::move(fullname), std::move(enumDecl));
+ 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 (!currentStruct) {
+ printf("[ERROR] BRUSSEL_CLASS must be used within a class or struct\n");
+ break;
+ }
+
+ // Always-on option
+ currentStruct->generating = true;
+
+ auto argList = TryConsumeDirectiveArgumentList(lexer);
+ 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: currentStruct->generatingInheritanceHiearchy = true; break;
+ case SMGO_COUNT: break;
+ }
+ }
+
+ goto endCaseCLEX_id;
+ }
+
+ case CD_ClassProperty: {
+ // Consume the directive
+ ++idx;
+ incrementTokenIdx = false;
+
+ if (!currentStruct ||
+ !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(lexer);
+ auto declOpt = TryConsumeMemberVariable(lexer);
+ 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;
+ }
+ }
+
+ currentStruct->memberVariables.push_back(std::move(decl));
+
+ goto endCaseCLEX_id;
+ }
+
+ case CD_ClassMethod: {
+ // Consume the directive
+ ++idx;
+ incrementTokenIdx = false;
+
+ goto endCaseCLEX_id;
+ }
+
+ case CD_Enum: {
+ // Consume the directive
+ ++idx;
+ incrementTokenIdx = false;
+
+ auto& optionsStrMap = RSTR_LUT(EnumMetaGenOptions);
+ auto argList = TryConsumeDirectiveArgumentList(lexer);
+
+ if (argList.size() < 1) {
+ printf("[ERROR] invalid syntax for BRUSSEL_ENUM\n");
+ break;
+ }
+
+ auto& enumName = argList[0][0]->text;
+ auto enumDecl = cgInput.FindEnum(Utils::MakeFullName(enumName, currentNamespace));
+ if (!enumDecl) {
+ printf("[ERROR] BRUSSEL_ENUM: referring to non-existent enum '%s'\n", enumName.c_str());
+ break;
+ }
+
+ auto& directiveOptions = argList[1];
+ EnumFlags<EnumMetaGenOptions> options;
+ for (auto optionTok : directiveOptions) {
+ auto iter = optionsStrMap.find(optionTok->text);
+ if (iter != optionsStrMap.end()) {
+ options |= iter->second;
+ } else {
+ printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionTok->text.c_str());
+ }
+ }
+
+ GenerateForEnum(cgHeaderOutput, cgSourceOutput, *enumDecl, options);
+
+ goto endCaseCLEX_id;
+ }
+
+ case CD_COUNT: break;
+ }
+
+ endCaseCLEX_id:;
+ } break;
+
+ case '{': {
+ currentBraceDepth++;
+ if (currentBraceDepth < 0) {
+ printf("[WARNING] unbalanced brace\n");
+ }
+ } break;
+
+ case '}': {
+ currentBraceDepth--;
+ if (currentBraceDepth < 0) {
+ printf("[WARNING] unbalanced brace\n");
+ }
+
+ if (!nsStack.empty()) {
+ auto& ns = nsStack.back();
+ if (ns.depth == currentBraceDepth) {
+ nsStack.pop_back();
+
+ if (!nsStack.empty()) {
+ currentNamespace = nsStack.back().ns;
+ } else {
+ currentNamespace = nullptr;
+ }
+ }
+ }
+
+ if (currentStruct &&
+ currentBraceDepth == currentStructBraceDepth)
+ {
+ // Exit struct
+
+ if (currentStruct->generating) {
+ GenerateForClassMetadata(cgHeaderOutput, cgSourceOutput, *currentStruct);
+ }
+ if (currentStruct->generatingInheritanceHiearchy) {
+ // NOTE: this option is transitive to all child classes (as long as they have the basic annotation)
+ // TODO
+ }
+
+ currentStruct = nullptr;
+ currentStructBraceDepth = 0;
+ }
+ } break;
+ }
+
+ if (incrementTokenIdx) {
+ ++idx;
+ }
+ }
+
+ if (currentBraceDepth != 0) {
+ printf("[WARNING] unbalanced brace at end of file.");
+ }
+
+ INPLACE_FMT(generatedHeaderInlName, "%.*s/%.*s.gh.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem));
+ Utils::WriteOutputFile(cgHeaderOutput, generatedHeaderInlName);
+ INPLACE_FMT(generatedSourceInlName, "%.*s/%.*s.gs.inl", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem));
+ Utils::WriteOutputFile(cgSourceOutput, generatedSourceInlName);
+ INPLACE_FMT(generatedCppName, "%.*s/%.*s.g.cpp", PRINTF_STRING_VIEW(state.outputDir), PRINTF_STRING_VIEW(filenameStem));
+ Utils::WriteOutputFile(cgStandaloneSourceOutput, generatedCppName);
+}
+
+enum InputOpcode {
+ IOP_ProcessSingleFile,
+ IOP_ProcessRecursively,
+ IOP_COUNT,
+};
+
+void HandleArgument(AppState& state, InputOpcode opcode, std::string_view operand) {
+ switch (opcode) {
+ case IOP_ProcessSingleFile: {
+ DEBUG_PRINTF("Processing single file %.*s\n", PRINTF_STRING_VIEW(operand));
+
+ fs::path path(operand);
+ auto filenameStem = path.stem().string();
+ auto source = Utils::ReadFileAsString(path);
+ HandleInputFile(state, filenameStem, source);
+ } 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();
+ auto pathExt = path.extension();
+ auto pathStem = path.stem();
+ if (pathExt != ".h" &&
+ pathExt != ".hpp")
+ {
+ continue;
+ }
+
+ DEBUG_PRINTF("Processing subfile %s\n", path.string().c_str());
+
+ auto filenameStem = pathStem.string();
+ auto source = Utils::ReadFileAsString(path);
+ HandleInputFile(state, filenameStem, source);
+ }
+ } 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 {
+ DEBUG_PRINTF("Unknown input opcode %s\n", text.data());
+ throw std::runtime_error("Unknown input opcode");
+ }
+}
+
+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 state;
+
+ // 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 < 2) {
+ // NOTE: keep in sync with various enum options and parser code
+ printf(&R"""(
+USAGE: codegen.exe <output path> [<opcode>:<input path>]...
+where <output path>: the directory to write generated contents to. This will NOT automatically create the directory.
+ <opcode> is one of:
+ "single" process this <input path> file only
+ "rec" starting at the given directory <input path>, recursively process all .h .hpp files
+)"""[1]);
+ return -1;
+ }
+
+ state.outputDir = std::string_view(argv[1]);
+ DEBUG_PRINTF("Outputting to directory %.*s.\n", PRINTF_STRING_VIEW(state.outputDir));
+
+ for (int i = 2; i < argc; ++i) {
+ const char* argRaw = argv[i];
+ std::string_view arg(argRaw);
+ DEBUG_PRINTF("Processing input command %s\n", argRaw);
+
+ 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(state, opcode, operand);
+ }
+ }
+
+ return 0;
+}