diff options
-rw-r--r-- | buildtools/codegen/CodegenDecl.cpp | 48 | ||||
-rw-r--r-- | buildtools/codegen/CodegenDecl.hpp | 33 | ||||
-rw-r--r-- | buildtools/codegen/CodegenInput.inl | 4 | ||||
-rw-r--r-- | buildtools/codegen/CodegenOutput.inl | 18 | ||||
-rw-r--r-- | buildtools/codegen/main.cpp | 212 | ||||
-rw-r--r-- | buildtools/codegen/tests/examples/TestEnum.hpp | 17 | ||||
-rw-r--r-- | source-common/Enum.hpp | 2 |
7 files changed, 295 insertions, 39 deletions
diff --git a/buildtools/codegen/CodegenDecl.cpp b/buildtools/codegen/CodegenDecl.cpp new file mode 100644 index 0000000..ac6bb01 --- /dev/null +++ b/buildtools/codegen/CodegenDecl.cpp @@ -0,0 +1,48 @@ +#include "CodegenDecl.hpp" + +#include <Utils.hpp> + +static EnumValuePattern NextPattern(EnumValuePattern val) { + return (EnumValuePattern)(val + 1); +} + +EnumValuePattern DeclEnum::CalcPattern() const { + if (elements.empty()) return EVP_Continuous; + + auto pattern = EVP_Continuous; +restart: + auto lastVal = elements[0].value; + for (size_t i = 1; i < elements.size(); ++i) { + auto currVal = elements[i].value; + switch (pattern) { + case EVP_Continuous: { + bool satisfy = lastVal + 1 == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + case EVP_Bits: { + bool satisfy = (lastVal << 1) == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + // A random pattern can match anything + case EVP_Random: + case EVP_COUNT: break; + } + lastVal = currVal; + } + + return pattern; +} + +void DeclEnum::CalcPatternIfNecessary() { + if (pattern == EVP_COUNT) { + CalcPattern(); + } +} diff --git a/buildtools/codegen/CodegenDecl.hpp b/buildtools/codegen/CodegenDecl.hpp index 3414d80..c2d8dbb 100644 --- a/buildtools/codegen/CodegenDecl.hpp +++ b/buildtools/codegen/CodegenDecl.hpp @@ -20,8 +20,22 @@ enum EnumUnderlyingType { EUT_COUNT, }; +enum EnumValuePattern { + // The numbers cover n..m with no gaps + EVP_Continuous, + // The numbers cover for i in n..m, 1 << i + // e.g. [0] = 1 << 0, + // [1] = 1 << 1. + // [2] = 1 << 2. etc. + EVP_Bits, + // The numbesr don't have a particular pattern + EVP_Random, + EVP_COUNT, +}; + struct DeclEnumElement { std::string name; + // TODO support int64_t, etc. enum underlying types uint64_t value; }; @@ -29,4 +43,23 @@ struct DeclEnum { std::string name; std::vector<DeclEnumElement> elements; EnumUnderlyingType underlyingType; + // Start with invalid value, calculate on demand + EnumValuePattern pattern = EVP_COUNT; + + EnumValuePattern CalcPattern() const; + void CalcPatternIfNecessary(); +}; + +struct DeclFunctionArgument { + std::string type; + std::string name; +}; + +struct DeclFunction { + // Things like extern, static, etc. that gets written before the function return type + std::string prefix; + std::string name; + std::string returnType; + std::vector<DeclFunctionArgument> arguments; + std::string body; }; diff --git a/buildtools/codegen/CodegenInput.inl b/buildtools/codegen/CodegenInput.inl index 9fae43c..80a39d0 100644 --- a/buildtools/codegen/CodegenInput.inl +++ b/buildtools/codegen/CodegenInput.inl @@ -13,7 +13,7 @@ class CodegenInput { private: std::vector<DeclEnum> mEnums; - robin_hood::unordered_map<std::string_view, size_t> mDeclByName; + robin_hood::unordered_map<std::string, size_t, StringHash, StringEqual> mDeclByName; public: void AddEnum(DeclEnum decl) { @@ -24,7 +24,7 @@ public: } #endif - mDeclByName.try_emplace(decl.name, mEnums.size()); + mDeclByName.try_emplace(std::string(decl.name), mEnums.size()); mEnums.push_back(std::move(decl)); } diff --git a/buildtools/codegen/CodegenOutput.inl b/buildtools/codegen/CodegenOutput.inl index 6d59301..752682c 100644 --- a/buildtools/codegen/CodegenOutput.inl +++ b/buildtools/codegen/CodegenOutput.inl @@ -15,6 +15,14 @@ struct CodegenOutputThing { class CodegenOutput { private: std::vector<CodegenOutputThing> mOutThings; + std::vector<DeclStruct> mOutStructs; + std::vector<DeclEnum> mOutEnums; + std::vector<DeclFunction> mOutFunctions; + +public: + std::string optionOutPrefix; + // Whether to add prefixes mOutPrefix to all global names or not + bool optionAutoAddPrefix : 1 = false; public: void AddOutputThing(CodegenOutputThing thing) { @@ -22,10 +30,16 @@ public: } void MergeContents(CodegenOutput other) { - std::move(other.mOutThings.begin(), other.mOutThings.end(), this->mOutThings.begin()); + std::move(other.mOutThings.begin(), other.mOutThings.end(), std::back_inserter(this->mOutThings)); } void Write(FILE* file) { - // TODO +#define WRITE_LITERAL(str) fwrite(str, sizeof(char), sizeof(str) - 1, file) + for (auto& thing : mOutThings) { + WRITE_LITERAL("// Output thing\n"); + fwrite(thing.text.c_str(), sizeof(char), thing.text.size(), file); + WRITE_LITERAL("\n"); + } +#undef WRITE_LITERAL } }; diff --git a/buildtools/codegen/main.cpp b/buildtools/codegen/main.cpp index 74acd1c..ed8cbbe 100644 --- a/buildtools/codegen/main.cpp +++ b/buildtools/codegen/main.cpp @@ -76,6 +76,7 @@ BSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { BSTR_LUT_MAP_FOR(CppKeyword); BSTR_LUT_MAP(CKw_Struct, "struct"); BSTR_LUT_MAP(CKw_Class, "class"); + BSTR_LUT_MAP(CKw_Enum, "enum"); } enum CodegenDirective { @@ -161,6 +162,10 @@ PeekDirectiveArgumentList(const std::vector<StbLexerToken>& tokens, size_t curre } } + if (!currentArg.empty()) { + result.push_back(std::move(currentArg)); + } + return { result, i }; } @@ -223,21 +228,95 @@ BSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { } enum EnumMetaGenOptions { - EMGO_Basic, + EMGO_ToString, + EMGO_ExcludeUseHeuristics, EMGO_COUNT, }; BSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { BSTR_LUT_MAP_FOR(EnumMetaGenOptions); - BSTR_LUT_MAP(EMGO_Basic, "GenBasic"); + BSTR_LUT_MAP(EMGO_ToString, "ToString"); + BSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); +} + +// I give up, hopefully nothing overflows this buffer +#define APPEND_SPRINTF(out, format, ...) \ + { \ + char buffer[65536]; \ + snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + out += buffer; \ + } + +std::string GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, bool useHeruistics) { + std::string arrayName; + APPEND_SPRINTF(arrayName, "gCG_%s_StringTable", decl.name.c_str()); + + CodegenOutputThing thing; + APPEND_SPRINTF(thing.text, "const char* %s[] = {\n", arrayName.c_str()); + for (auto& elm : decl.elements) { + if (useHeruistics && elm.name.ends_with("COUNT")) { + continue; + } + + APPEND_SPRINTF(thing.text, "\"%s\",\n", elm.name.c_str()); + } + thing.text += "};\n"; + out.AddOutputThing(std::move(thing)); + + return arrayName; +} + +std::string GenerateEnumStringMap(CodegenOutput& out, const DeclEnum& decl, bool useHeruistics) { + std::string mapName; + // TODO + + return mapName; } void GenerateForEnum(CodegenOutput& out, const DeclEnum& decl, EnumFlags<EnumMetaGenOptions> options) { + bool useExcludeHeuristics = options.IsSet(EMGO_ExcludeUseHeuristics); + + if (options.IsSet(EMGO_ToString)) { + // Generate name lookup table + + switch (decl.CalcPattern()) { + case EVP_Continuous: { + auto arrayName = GenerateEnumStringArray(out, decl, useExcludeHeuristics); + int minVal = decl.elements.front().value; + int maxVal = decl.elements.back().value; + if (useExcludeHeuristics && + decl.elements.back().name.ends_with("COUNT") && + decl.elements.size() >= 2) + { + // Skip the last *_COUNT element if instructed to use heuristics + maxVal = decl.elements[decl.elements.size() - 2].value; + } + + CodegenOutputThing lookupFunction; + APPEND_SPRINTF(lookupFunction.text, "std::string_view Stringify%s(%s value) {\n", decl.name.c_str(), decl.name.c_str()); + APPEND_SPRINTF(lookupFunction.text, " if (value < 0 || value >= %d) return {};\n", maxVal); + APPEND_SPRINTF(lookupFunction.text, " return %s[value - %d];\n", arrayName.c_str(), minVal); + lookupFunction.text += "}\n"; + + out.AddOutputThing(std::move(lookupFunction)); + } break; + + case EVP_Bits: { + auto arrayName = GenerateEnumStringArray(out, decl, useExcludeHeuristics); + } break; + + case EVP_Random: { + auto mapName = GenerateEnumStringMap(out, decl, useExcludeHeuristics); + } break; + + case EVP_COUNT: break; + } + } } void HandleInputFile(AppState& state, std::string_view source) { auto tokens = RecordTokens(source); - size_t tokenIdx = 0; + size_t idx = 0; #if CODEGEN_DEBUG_PRINT printf("BEGIN tokens\n"); @@ -247,12 +326,12 @@ void HandleInputFile(AppState& state, std::string_view source) { printf("END tokens\n"); #endif - CodegenInput input; - CodegenOutput output; + CodegenInput fileInput; + CodegenOutput fileOutput; int bracePairDepth = 0; - while (tokenIdx < tokens.size()) { - auto& token = tokens[tokenIdx]; + while (idx < tokens.size()) { + auto& token = tokens[idx]; bool incrementTokenIdx = true; @@ -265,25 +344,77 @@ void HandleInputFile(AppState& state, std::string_view source) { if (iter != map.end()) { keyword = iter->second; } else { - break; + keyword = CKw_COUNT; // Skip keyword section } } switch (keyword) { case CKw_Struct: case CKw_Class: { - auto& idenTok = tokens[tokenIdx + 1]; // TODO handle end of list + auto& idenTok = tokens[idx + 1]; // TODO handle end of list DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); - } break; + goto endIdenCase; + } case CKw_Enum: { - StbLexerToken* idenTok = &token + 1; // TODO handle end of list - if (idenTok->text == "class") { - idenTok += 1; - DEBUG_PRINTF("[DEBUG] found enum class named %s\n", idenTok->text.c_str()); + // Consume the "enum" keyword + ++idx; + incrementTokenIdx = false; + + DeclEnum enumDecl; + enumDecl.underlyingType = EUT_Int32; // TODO + + if (tokens[idx].text == "class") { + // Consume the "class" keyword + ++idx; + DEBUG_PRINTF("[DEBUG] found enum class named %s\n", tokens[idx].text.c_str()); } else { - DEBUG_PRINTF("[DEBUG] found enum named %s\n", idenTok->text.c_str()); + DEBUG_PRINTF("[DEBUG] found enum named %s\n", tokens[idx].text.c_str()); + } + + // Consume the enum name identifier + enumDecl.name = tokens[idx].text; + ++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: { + + } break; + + case CLEX_ext_single_char: { + switch (token.text[0]) { + case '{': { + ++enumBraceDepth; + } break; + + case '}': { + --enumBraceDepth; + ++enumClosingBraceCount; + } break; + } + } break; + } + + ++idx; } - } break; + + fileInput.AddEnum(std::move(enumDecl)); + goto endIdenCase; + } case CKw_COUNT: break; } @@ -295,24 +426,33 @@ void HandleInputFile(AppState& state, std::string_view source) { if (iter != map.end()) { directive = iter->second; } else { - break; + directive = CD_COUNT; // Skip directive section } } switch (directive) { case CD_ClassInfo: { // TODO - } break; + goto endIdenCase; + } case CD_EnumInfo: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + auto& optionsStrMap = BSTR_LUT_S2V(EnumMetaGenOptions); - auto [argList, newIdx] = PeekDirectiveArgumentList(tokens, tokenIdx); + auto [argList, newIdx] = PeekDirectiveArgumentList(tokens, idx); if (argList.size() < 1) { printf("[ERROR] invalid syntax for BRUSSEL_ENUM\n"); - break; + break; // TODO handle this error case gracefully (advance to semicolon?) } auto& enumName = argList[0][0]->text; - auto enumDecl = input.FindEnumByName(enumName); + auto enumDecl = fileInput.FindEnumByName(enumName); + if (!enumDecl) { + printf("[ERROR] BRUSSEL_ENUM: referring to non-existent enum '%s'\n", enumName.c_str()); + break; + } auto& directiveOptions = argList[1]; EnumFlags<EnumMetaGenOptions> options; @@ -321,19 +461,23 @@ void HandleInputFile(AppState& state, std::string_view source) { if (iter != optionsStrMap.end()) { options |= iter->second; } else { - printf("[ERROR] invalid option '%s' for BRUSSEL_ENUM", optionTok->text.c_str()); + printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionTok->text.c_str()); } } - GenerateForEnum(output, *enumDecl, options); + GenerateForEnum(fileOutput, *enumDecl, options); - tokenIdx = newIdx; + idx = newIdx; incrementTokenIdx = false; - } break; + goto endIdenCase; + } case CD_COUNT: break; } - } break; + + endIdenCase: + break; + } case '{': { bracePairDepth++; @@ -347,13 +491,15 @@ void HandleInputFile(AppState& state, std::string_view source) { } if (incrementTokenIdx) { - ++tokenIdx; + ++idx; } } if (bracePairDepth != 0) { printf("[WARNING] unbalanced brace at end of file."); } + + state.mainOutput.MergeContents(std::move(fileOutput)); } std::string ReadFileAtOnce(const fs::path& path) { @@ -436,12 +582,12 @@ int main(int argc, char* argv[]) { 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 _file_ to write generated contents to - <opcode> is one of: - "single" process this <input path> file only - "rec" starting at the given directory <input path>, recursively process all .h .c .hpp .cpp files -)"""[1]); + USAGE: codegen.exe <output path> [<opcode>:<input path>]... + where <output path>: the _file_ to write generated contents to + <opcode> is one of: + "single" process this <input path> file only + "rec" starting at the given directory <input path>, recursively process all .h .c .hpp .cpp files + )"""[1]); return -1; } diff --git a/buildtools/codegen/tests/examples/TestEnum.hpp b/buildtools/codegen/tests/examples/TestEnum.hpp index 2a93c01..c498cd9 100644 --- a/buildtools/codegen/tests/examples/TestEnum.hpp +++ b/buildtools/codegen/tests/examples/TestEnum.hpp @@ -5,4 +5,19 @@ enum MyEnum { EnumElement2, EnumElement3, }; -BRUSSEL_ENUM(MyEnum, GenBasic); +BRUSSEL_ENUM(MyEnum, ToString); + +enum CountedEnumAll { + CEA_Foo, + CEA_Bar, + CEA_COUNT, +}; +BRUSSEL_ENUM(CountedEnumAll, ToString); + +enum CountedEnum { + CE_Foo, + CE_Bar, + CE_FooBar, + CE_COUNT, +}; +BRUSSEL_ENUM(CountedEnum, ToString ExcludeHeuristics); diff --git a/source-common/Enum.hpp b/source-common/Enum.hpp index e8750f2..8ad75ba 100644 --- a/source-common/Enum.hpp +++ b/source-common/Enum.hpp @@ -18,7 +18,7 @@ public: } EnumFlags(TEnum e) - : mValue{ 1 << static_cast<Underlying>(e) } { + : mValue{ static_cast<Underlying>(1) << static_cast<Underlying>(e) } { } bool IsSet(EnumFlags mask) const { |