diff options
author | rtk0c <[email protected]> | 2022-05-30 15:56:29 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-05-30 15:56:29 -0700 |
commit | 80afa67d2b9f1c0605696a3fd69058544fe12fe4 (patch) | |
tree | 3dfe2a6f45e4f5ca8ad534baf06d76d9c558333b | |
parent | 0d92ecfdbfc875a099d9e83714b3a2209668fca5 (diff) | |
parent | 7d8bca09b3c4bf1418e758bd3bd0d6f85672153e (diff) |
Changeset: 53
39 files changed, 1460 insertions, 12 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6720e77..7d4ecf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(ProjectBrussel LANGUAGES C CXX) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup() +include(buildtools/cmake/RTTI.cmake) include(buildtools/cmake/Win32Subsystem.cmake) find_package(OpenGL REQUIRED) @@ -13,6 +14,94 @@ add_subdirectory(3rdparty/imgui) add_subdirectory(3rdparty/imguicolortextedit) add_subdirectory(3rdparty/tracy) +# ============================================================================== + +file(GLOB_RECURSE things_common_SOURCES source-common/*.c source-common/*.cpp) +add_library(things_common OBJECT ${things_common_SOURCES}) + +set_target_properties(things_common PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + + # Many files include platform headers, we don't want to leak them - it's just simpler to disable unity build for everything + UNITY_BUILD OFF +) + +target_include_directories(things_common PUBLIC source-common) +target_link_libraries(things_common PUBLIC + # External dependencies + ${CONAN_LIBS} + glm::glm +) + +# ============================================================================== + +# NOTE: delibrately not recursive, see README.md in the folder for details +file(GLOB codegen_SOURCES buildtools/codegen/*.c buildtools/codegen/*.cpp) +add_executable(codegen ${codegen_SOURCES}) + +set_target_properties(codegen PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + UNITY_BUILD OFF +) + +target_link_libraries(codegen PRIVATE + # External dependencies + ${CONAN_LIBS} + + # Project internal components + things_common +) + +target_flag_rtti(codegen OFF) + +option(BRUSSEL_CODEGEN_DEBUG_PRINT "Enable debug printing in the code generator or not." OFF) +if(BRUSSEL_CODEGEN_DEBUG_PRINT) + target_compile_definitions(codegen PRIVATE CODEGEN_DEBUG_PRINT=1) +endif() + +file(GLOB_RECURSE things_codegen_base_SOURCES source-codegen-base/*.c source-codegen-base/*.cpp) +add_library(things_codegen_base OBJECT ${things_codegen_base_SOURCES}) + +set_target_properties(things_codegen_base PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF +) + +target_include_directories(things_codegen_base PUBLIC source-codegen-base) +target_link_libraries(things_codegen_base PUBLIC + # External dependencies + ${CONAN_LIBS} + + # Project internal components + things_common +) + +# TODO support reading all files from the target, instead of manually supplying a search dir +function(target_gen_metadata TARGET_NAME SEARCH_DIR) + get_target_property(TARGET_SOURCES ${TARGET_NAME} SOURCES) + + set(OUTPUT_DIR ${CMAKE_BINARY_DIR}/generated/${TARGET_NAME}) + set(OUTPUT_FILES + ${OUTPUT_DIR}/generated/GeneratedCode.hpp + ${OUTPUT_DIR}/generated/GeneratedCode.cpp + ) + add_custom_command( + OUTPUT ${OUTPUT_FILES} + COMMAND codegen ${OUTPUT_DIR}/generated rec:${SEARCH_DIR} + DEPENDS ${TARGET_SOURCES} + ) + + target_include_directories(${TARGET_NAME} PRIVATE ${OUTPUT_DIR}) + target_sources(${TARGET_NAME} PRIVATE ${OUTPUT_FILES}) +endfunction() + +# ============================================================================== + # add_executable requires at least one source file add_executable(${PROJECT_NAME} dummy.c) add_subdirectory(source) @@ -22,8 +111,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD_UNIQUE_ID "${PROJECT_NAME}_UNITY_ID" ) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) @@ -39,6 +128,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ) target_link_libraries(${PROJECT_NAME} PRIVATE + # External dependencies ${CONAN_LIBS} OpenGL::GL glfw @@ -46,6 +136,10 @@ target_link_libraries(${PROJECT_NAME} PRIVATE imgui ImGuiColorTextEdit tracy + + # Project internal components + things_common + things_codegen_base ) target_use_windows_subsystem(${PROJECT_NAME}) @@ -79,3 +173,6 @@ if(BRUSSEL_ENABLE_ASAN) -fno-omit-frame-pointer ) endif() + +get_filename_component(METADATA_INP_DIR "source" ABSOLUTE) +target_gen_metadata(${PROJECT_NAME} ${METADATA_INP_DIR}) @@ -5,3 +5,12 @@ + [Urho3D](https://urho3d.io/) + [godot](https://godotengine.org/) + Everything else in `3rdparty/` + +## Project Structure +- `buildtools` + - `cmake`: CMake scripts consumed by the root `CMakeLists.txt`. + - `codegen`: Code generator similar to Qt MOC. + Currently runs on: `source`. +- `source-common`: Code that's compiled as a part of all targets. Check each target's build script for details. +- `source-codegen-base`: Code that's consumed along with output of `buildtools/codegen`. +- `source`: The main game. diff --git a/buildtools/cmake/RTTI.cmake b/buildtools/cmake/RTTI.cmake new file mode 100644 index 0000000..b948497 --- /dev/null +++ b/buildtools/cmake/RTTI.cmake @@ -0,0 +1,31 @@ +function(target_flag_rtti_msvc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE /GR) + else() + target_compile_options(${TARGET_NAME} PRIVATE /GR-) + endif() +endfunction() + +function(target_flag_rtti_gcc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE -frtti) + else() + target_compile_options(${TARGET_NAME} PRIVATE -fno-rtti) + endif() +endfunction() + +function(target_flag_rtti TARGET_NAME ENABLED) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_flag_rtti_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + target_flag_rtti_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + target_flag_rtti_gcc(${TARGET_NAME} ${ENABLED}) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_flag_rtti_gcc(${TARGET_NAME} ${ENABLED}) + else() + message(FATAL "target_flag_rtti(): Unknown compiler ${CMAKE_CXX_COMPILER_ID}") + endif() +endfunction() diff --git a/buildtools/codegen/CodegenConfig.hpp b/buildtools/codegen/CodegenConfig.hpp new file mode 100644 index 0000000..b9dc56c --- /dev/null +++ b/buildtools/codegen/CodegenConfig.hpp @@ -0,0 +1,11 @@ +#pragma once + +#ifndef CODEGEN_DEBUG_PRINT +# define CODEGEN_DEBUG_PRINT 0 +#endif + +#if CODEGEN_DEBUG_PRINT +# define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +# define DEBUG_PRINTF(...) +#endif diff --git a/buildtools/codegen/CodegenDecl.cpp b/buildtools/codegen/CodegenDecl.cpp new file mode 100644 index 0000000..7cf21ce --- /dev/null +++ b/buildtools/codegen/CodegenDecl.cpp @@ -0,0 +1,49 @@ +#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; +} + +EnumValuePattern DeclEnum::GetPattern() const { + if (pattern == EVP_COUNT) { + pattern = CalcPattern(); + } + return pattern; +} diff --git a/buildtools/codegen/CodegenDecl.hpp b/buildtools/codegen/CodegenDecl.hpp new file mode 100644 index 0000000..32d5445 --- /dev/null +++ b/buildtools/codegen/CodegenDecl.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include <string> +#include <vector> + +struct DeclNamespace { + DeclNamespace* container = nullptr; + std::string name; + std::string_view fullname; // View into storage map key +}; + +// Structs or classes +struct DeclStruct { + DeclNamespace* container = nullptr; + std::string name; +}; + +enum EnumUnderlyingType { + EUT_Int8, + EUT_Int16, + EUT_Int32, + EUT_Int64, + EUT_Uint8, + EUT_Uint16, + EUT_Uint32, + EUT_Uint64, + 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; +}; + +struct DeclEnum { + DeclNamespace* container = nullptr; + std::string name; + std::vector<DeclEnumElement> elements; + EnumUnderlyingType underlyingType; + // Start with invalid value, calculate on demand + mutable EnumValuePattern pattern = EVP_COUNT; + + EnumValuePattern CalcPattern() const; + EnumValuePattern GetPattern() const; +}; + +struct DeclFunctionArgument { + std::string type; + std::string name; +}; + +struct DeclFunction { + DeclNamespace* container = nullptr; + // 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 new file mode 100644 index 0000000..0809e7f --- /dev/null +++ b/buildtools/codegen/CodegenInput.inl @@ -0,0 +1,69 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" + +#include "CodegenUtils.inl" + +#include <Utils.hpp> + +#include <robin_hood.h> +#include <cinttypes> +#include <string> +#include <string_view> +#include <vector> + +using namespace std::literals; + +class CodegenInput { +private: + std::vector<DeclEnum> mEnums; + robin_hood::unordered_flat_map<std::string, size_t, StringHash, StringEqual> mDeclByName; + robin_hood::unordered_node_map<std::string, DeclNamespace, StringHash, StringEqual> mNamespaces; + +public: + void AddEnum(std::string fullname, DeclEnum decl) { +#if CODEGEN_DEBUG_PRINT + printf("Committed enum '%s'\n", decl.name.c_str()); + for (auto& elm : decl.elements) { + printf(" - element %s = %" PRId64 "\n", elm.name.c_str(), elm.value); + } +#endif + + mDeclByName.try_emplace(std::move(fullname), mEnums.size()); + mEnums.push_back(std::move(decl)); + } + + DeclNamespace* AddNamespace(DeclNamespace ns) { + auto path = Utils::MakeFullName(""sv, &ns); + auto [iter, success] = mNamespaces.try_emplace(std::move(path), std::move(ns)); + auto& nsRef = iter->second; + if (success) { + nsRef.fullname = iter->first; + } + return &nsRef; + } + + const DeclEnum* FindEnumByName(std::string_view name) const { + // TODO handle multiple kinds of decl + auto iter = mDeclByName.find(name); + if (iter != mDeclByName.end()) { + return &mEnums[iter->second]; + } else { + return nullptr; + } + } + + const DeclNamespace* FindNamespace(std::string_view fullname) const { + auto iter = mNamespaces.find(fullname); + if (iter != mNamespaces.end()) { + return &iter->second; + } else { + return nullptr; + } + } + + DeclNamespace* FindNamespace(std::string_view name) { + return const_cast<DeclNamespace*>(const_cast<const CodegenInput*>(this)->FindNamespace(name)); + } +}; diff --git a/buildtools/codegen/CodegenMacros.hpp b/buildtools/codegen/CodegenMacros.hpp new file mode 100644 index 0000000..84c9d09 --- /dev/null +++ b/buildtools/codegen/CodegenMacros.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include <algorithm> + +// I give up, hopefully nothing overflows this buffer +// TODO handle buffer sizing properly + +#define APPEND_LIT(out, str) out += str + +#define APPEND_FMT(out, format, ...) \ + { \ + char buffer[65536]; \ + snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + out += buffer; \ + } + +#define WRITE_LIT(file, str) fwrite(str, sizeof(char), sizeof(str) - 1, file) + +// NOTE: snprintf() returns the size written (given an infinite buffer) not including \0 +#define WRITE_FMT(file, format, ...) \ + { \ + char buffer[65536]; \ + int size = snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + fwrite(buffer, sizeof(char), std::min<int>(size, sizeof(buffer)), file); \ + } + +#define APPEND_LIT_LN(out, str) APPEND_LIT(out, (str "\n")) +#define APPEND_FMT_LN(out, format, ...) APPEND_FMT(out, (format "\n"), __VA_ARGS__) +#define WRITE_LIT_LN(out, str) WRITE_LIT(out, (str "\n")) +#define WRITE_FMT_LN(out, format, ...) WRITE_FMT(out, (format "\n"), __VA_ARGS__) diff --git a/buildtools/codegen/CodegenOutput.inl b/buildtools/codegen/CodegenOutput.inl new file mode 100644 index 0000000..ff7b912 --- /dev/null +++ b/buildtools/codegen/CodegenOutput.inl @@ -0,0 +1,76 @@ +#pragma once + +#include "CodegenDecl.hpp" +#include "CodegenMacros.hpp" + +#include <Utils.hpp> + +#include <robin_hood.h> +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <string> +#include <vector> + +// A generic "thing" (could be anything, comments, string-concated functionsm, etc.) to spit into the output file +struct CodegenOutputThing { + std::string text; +}; + +class CodegenOutput { +private: + robin_hood::unordered_set<std::string, StringHash, StringEqual> mRequestIncludes; + 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 AddRequestInclude(std::string_view include) { + if (!mRequestIncludes.contains(include)) { + mRequestIncludes.insert(std::string(include)); + } + } + + void AddOutputThing(CodegenOutputThing thing) { + mOutThings.push_back(std::move(thing)); + } + + void MergeContents(CodegenOutput other) { + std::move(other.mOutThings.begin(), other.mOutThings.end(), std::back_inserter(this->mOutThings)); + std::move(other.mOutStructs.begin(), other.mOutStructs.end(), std::back_inserter(this->mOutStructs)); + std::move(other.mOutEnums.begin(), other.mOutEnums.end(), std::back_inserter(this->mOutEnums)); + std::move(other.mOutFunctions.begin(), other.mOutFunctions.end(), std::back_inserter(this->mOutFunctions)); + } + + void Write(FILE* file) const { + for (auto& include : mRequestIncludes) { + // TODO how to resolve to the correct include paths? + WRITE_FMT_LN(file, "#include <%s>", include.c_str()); + } + + for (auto& thing : mOutThings) { + fwrite(thing.text.c_str(), sizeof(char), thing.text.size(), file); + WRITE_LIT(file, "\n"); + } + + for (auto& declStruct : mOutStructs) { + WRITE_FMT_LN(file, "struct %s {", declStruct.name.c_str()); + // TODO + WRITE_LIT_LN(file, "};"); + } + + for (auto& declEnum : mOutEnums) { + // TODO + } + + for (auto& declFunc : mOutFunctions) { + // TODO + } + } +}; diff --git a/buildtools/codegen/CodegenUtils.inl b/buildtools/codegen/CodegenUtils.inl new file mode 100644 index 0000000..f9d913e --- /dev/null +++ b/buildtools/codegen/CodegenUtils.inl @@ -0,0 +1,106 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenMacros.hpp" + +#include "CodegenOutput.inl" + +#include <Macros.hpp> +#include <ScopeGuard.hpp> + +#include <cstdio> +#include <cstdlib> +#include <filesystem> + +namespace Utils { + +std::string ReadFileAsString(const std::filesystem::path& path) { + auto file = Utils::OpenCstdioFile(path, Utils::Read); + if (!file) throw std::runtime_error("Failed to open source file."); + DEFER { fclose(file); }; + + fseek(file, 0, SEEK_END); + auto fileSize = ftell(file); + rewind(file); + + std::string result(fileSize, '\0'); + fread(result.data(), fileSize, 1, file); + + return result; +} + +bool WriteOutputFile(const CodegenOutput& output, std::string_view dir, std::string_view filename, std::string_view additionalSuffix = {}) { + char path[2048]; + snprintf(path, sizeof(path), "%.*s/%.*s%.*s", PRINTF_STRING_VIEW(dir), PRINTF_STRING_VIEW(filename), PRINTF_STRING_VIEW(additionalSuffix)); + + auto outputFile = Utils::OpenCstdioFile(path, Utils::WriteTruncate); + if (!outputFile) { + printf("[ERROR] unable to open output file %s\n", path); + return false; + } + DEFER { fclose(outputFile); }; + + DEBUG_PRINTF("Writing output %s\n", path); + output.Write(outputFile); + + return true; +} + +std::string MakeFullName(std::string_view name, DeclNamespace* ns = nullptr) { + size_t length = 0; + std::vector<std::string_view> components; + if (!name.empty()) { + components.push_back(name); + length += name.length(); + } + + DeclNamespace* currentNamespace = ns; + while (currentNamespace) { + components.push_back(currentNamespace->name); + length += currentNamespace->name.size() + /*::*/ 2; + currentNamespace = currentNamespace->container; + } + + std::string fullname; + fullname.reserve(length); + for (auto it = components.rbegin(); it != components.rend(); ++it) { + fullname += *it; + fullname += "::"; + } + // Get rid of the last "::" + fullname.pop_back(); + fullname.pop_back(); + + return fullname; +} + +void ProduceGeneratedHeaderFileHeader(CodegenOutput& output) { + output.AddOutputThing(CodegenOutputThing{ + .text = &R"""( +// This file is generated. Any changes will be overidden when building. +#pragma once + +#include <MetadataBase.hpp> + +#include <cstddef> +#include <cstdint> +)"""[1], + }); +} + +void ProduceGeneratedSourceFileHeader(CodegenOutput& output) { + output.AddOutputThing(CodegenOutputThing{ + .text = &R"""( +// This file is generated. Any changes will be overidden when building. +#include "GeneratedCode.hpp" + +#include <cstddef> +#include <cstdint> +#include <frozen/string.h> +#include <frozen/unordered_map.h> +using namespace std::literals; + )"""[1], + }); +} + +} // namespace Utils diff --git a/buildtools/codegen/README.md b/buildtools/codegen/README.md new file mode 100644 index 0000000..7164132 --- /dev/null +++ b/buildtools/codegen/README.md @@ -0,0 +1,5 @@ +# Code Generator +The main code generator. + +## Folder structure +The main program's source files are all located in this folder directly. Text tempaltes are located in `templates/` and none of the files are compiled (even if they end with .c or .cpp). diff --git a/buildtools/codegen/main.cpp b/buildtools/codegen/main.cpp new file mode 100644 index 0000000..2c259a4 --- /dev/null +++ b/buildtools/codegen/main.cpp @@ -0,0 +1,757 @@ +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenMacros.hpp" + +#include "CodegenInput.inl" +#include "CodegenOutput.inl" +#include "CodegenUtils.inl" + +#include <Enum.hpp> +#include <LookupTable.hpp> +#include <Macros.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <frozen/string.h> +#include <frozen/unordered_map.h> +#include <robin_hood.h> +#include <stb_c_lexer.h> +#include <cinttypes> +#include <cstdlib> +#include <filesystem> +#include <memory> +#include <span> +#include <string> +#include <string_view> + +using namespace std::literals; +namespace fs = std::filesystem; + +// TODO handle namespace +// TODO support codegen target in .cpp files + +struct AppState { + std::string_view outputDir; + CodegenOutput mainHeaderOutput; + CodegenOutput mainSourceOutput; +}; + +enum { + CLEX_ext_single_char = CLEX_first_unused_token, + CLEX_ext_COUNT, +}; + +STR_LUT_DECL(ClexNames, CLEX_eof, CLEX_ext_COUNT) { + STR_LUT_MAP_FOR(ClexNames); + STR_LUT_MAP_ENUM(CLEX_intlit); + STR_LUT_MAP_ENUM(CLEX_floatlit); + STR_LUT_MAP_ENUM(CLEX_id); + STR_LUT_MAP_ENUM(CLEX_dqstring); + STR_LUT_MAP_ENUM(CLEX_sqstring); + STR_LUT_MAP_ENUM(CLEX_charlit); + STR_LUT_MAP_ENUM(CLEX_eq); + STR_LUT_MAP_ENUM(CLEX_noteq); + STR_LUT_MAP_ENUM(CLEX_lesseq); + STR_LUT_MAP_ENUM(CLEX_greatereq); + STR_LUT_MAP_ENUM(CLEX_andand); + STR_LUT_MAP_ENUM(CLEX_oror); + STR_LUT_MAP_ENUM(CLEX_shl); + STR_LUT_MAP_ENUM(CLEX_shr); + STR_LUT_MAP_ENUM(CLEX_plusplus); + STR_LUT_MAP_ENUM(CLEX_minusminus); + STR_LUT_MAP_ENUM(CLEX_pluseq); + STR_LUT_MAP_ENUM(CLEX_minuseq); + STR_LUT_MAP_ENUM(CLEX_muleq); + STR_LUT_MAP_ENUM(CLEX_diveq); + STR_LUT_MAP_ENUM(CLEX_modeq); + STR_LUT_MAP_ENUM(CLEX_andeq); + STR_LUT_MAP_ENUM(CLEX_oreq); + STR_LUT_MAP_ENUM(CLEX_xoreq); + STR_LUT_MAP_ENUM(CLEX_arrow); + STR_LUT_MAP_ENUM(CLEX_eqarrow); + STR_LUT_MAP_ENUM(CLEX_shleq); + STR_LUT_MAP_ENUM(CLEX_shreq); + STR_LUT_MAP_ENUM(CLEX_ext_single_char); +} + +enum CppKeyword { + CKw_Namespace, + CKw_Struct, + CKw_Class, + CKw_Enum, + CKw_COUNT, +}; + +BSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { + BSTR_LUT_MAP_FOR(CppKeyword); + BSTR_LUT_MAP(CKw_Namespace, "namespace"); + BSTR_LUT_MAP(CKw_Struct, "struct"); + BSTR_LUT_MAP(CKw_Class, "class"); + BSTR_LUT_MAP(CKw_Enum, "enum"); +} + +enum CodegenDirective { + CD_ClassInfo, + CD_EnumInfo, + CD_COUNT, +}; + +BSTR_LUT_DECL(CodegenDirective, 0, CD_COUNT) { + BSTR_LUT_MAP_FOR(CodegenDirective); + BSTR_LUT_MAP(CD_ClassInfo, "BRUSSEL_CLASS"); + BSTR_LUT_MAP(CD_EnumInfo, "BRUSSEL_ENUM"); +} + +struct StbLexerToken { + std::string text; + // Can either be CLEX_* or CLEX_ext_* values + int type; +}; + +bool StbTokenIsSingleChar(int lexerToken) { + return lexerToken >= 0 && lexerToken < 256; +} + +bool StbTokenIsMultiChar(int lexerToken) { + return !StbTokenIsMultiChar(lexerToken); +} + +void CheckBraceDepth(int braceDpeth) { + if (braceDpeth < 0) { + printf("[WARNING] unbalanced brace\n"); + } +} + +const StbLexerToken* +PeekTokenOfTypeAt(const std::vector<StbLexerToken>& tokens, size_t idx, int type) { + auto& token = tokens[idx]; + if (token.type != type) { + return nullptr; + } + + return &token; +} + +std::pair<const StbLexerToken*, size_t> +PeekTokenOfType(const std::vector<StbLexerToken>& tokens, size_t current, int type) { + for (size_t i = current; i < tokens.size(); ++i) { + if (auto token = PeekTokenOfTypeAt(tokens, i, type)) { + return { token, i }; + } + } + return { nullptr, current }; +} + +std::pair<std::vector<std::vector<const StbLexerToken*>>, size_t> +PeekDirectiveArgumentList(const std::vector<StbLexerToken>& tokens, size_t current) { + std::vector<std::vector<const StbLexerToken*>> result; + decltype(result)::value_type currentArg; + + size_t i = current; + int parenDepth = 0; + for (; i < tokens.size(); ++i) { + auto& token = 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 + 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)); + } + + return { result, i }; +} + +std::vector<StbLexerToken> RecordTokens(std::string_view source) { + stb_lexer lexer; + char stringStorage[65536]; + const char* srcBegin = source.data(); + const char* srcEnd = srcBegin + source.length(); + stb_c_lexer_init(&lexer, srcBegin, srcEnd, stringStorage, sizeof(stringStorage)); + + std::vector<StbLexerToken> tokens; + while (true) { + // See stb_c_lexer.h's comments, here are a few additinos that aren't made clear in the file: + // - `lexer->token` (noted as "token" below) after calling stb_c_lexer_get_token() contains either: + // 1. 0 <= token < 256: an ASCII character (more precisely a single char that the lexer ate; technically can be an incomplete code unit) + // 2. token < 0: an unknown token + // 3. One of the `CLEX_*` enums: a special, recognized token such as an operator + + int stbToken = stb_c_lexer_get_token(&lexer); + if (stbToken == 0) { + // EOF + break; + } + + if (lexer.token == CLEX_parse_error) { + printf("[ERROR] stb_c_lexer countered a parse error.\n"); + // TODO how to handle? + continue; + } + + StbLexerToken token; + if (StbTokenIsSingleChar(lexer.token)) { + token.type = CLEX_ext_single_char; + token.text = std::string(1, lexer.token); + } else { + token.type = lexer.token; + // WORKAROUND: use null terminated string, stb_c_lexer doens't set string_len properly when parsing identifiers + token.text = std::string(lexer.string); + } + tokens.push_back(std::move(token)); + token = {}; + } + return tokens; +} + +enum StructMetaGenOptions { + SMGO_InheritanceHiearchy, + SMGO_PublicFields, + SMGO_ProtectedFields, + SMGO_PrivateFields, + SMGO_COUNT, +}; + +BSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { + BSTR_LUT_MAP_FOR(StructMetaGenOptions); + BSTR_LUT_MAP(SMGO_InheritanceHiearchy, "GenInheritanceHiearchy"); + BSTR_LUT_MAP(SMGO_PublicFields, "GenPublicFields"); + BSTR_LUT_MAP(SMGO_ProtectedFields, "GenProtectedFields"); + BSTR_LUT_MAP(SMGO_PrivateFields, "GenPrivateFields"); +} + +enum EnumMetaGenOptions { + EMGO_ToString, + EMGO_FromString, + EMGO_ExcludeUseHeuristics, + EMGO_COUNT, +}; + +BSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { + BSTR_LUT_MAP_FOR(EnumMetaGenOptions); + BSTR_LUT_MAP(EMGO_ToString, "ToString"); + BSTR_LUT_MAP(EMGO_FromString, "FromString"); + BSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); +} + +std::string GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, bool useHeruistics) { + std::string arrayName; + APPEND_FMT(arrayName, "gCG_%s_Val2Str", decl.name.c_str()); + + CodegenOutputThing thing; + APPEND_FMT_LN(thing.text, "const char* %s[] = {", arrayName.c_str()); + for (auto& elm : decl.elements) { + if (useHeruistics && elm.name.ends_with("COUNT")) { + continue; + } + + APPEND_FMT_LN(thing.text, "\"%s\",", elm.name.c_str()); + } + APPEND_LIT_LN(thing.text, "};"); + 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& 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)); + } + + 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 + + switch (decl.GetPattern()) { + case EVP_Continuous: { + auto arrayName = GenerateEnumStringArray(sourceOut, decl, useExcludeHeuristics); + int minVal = filteredElements.empty() ? 0 : filteredElements.front().value; + int maxVal = filteredElements.empty() ? 0 : filteredElements.back().value; + + 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, " if (value < %d || value > %d) return {};", minVal, maxVal); + APPEND_FMT_LN(o, " return %s[value - %d];", arrayName.c_str(), minVal); + APPEND_LIT_LN(o, "}"); + } + + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } break; + + case EVP_Bits: { + auto arrayName = GenerateEnumStringArray(sourceOut, decl, useExcludeHeuristics); + // TODO + } break; + + case EVP_Random: { + auto mapName = GenerateEnumStringMap(sourceOut, decl, useExcludeHeuristics); + // TODO + } break; + + case EVP_COUNT: break; + } + } + + if (options.IsSet(EMGO_FromString)) { + // Generate string -> value lookup table + char mapName[1024]; + // TODO mangle to prevent name conflicts of enum in different namespaces + snprintf(mapName, sizeof(mapName), "gCG_%s_Str2Val", decl.name.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(), mapName); + for (auto& elm : filteredElements) { + APPEND_FMT_LN(o, "{\"%s\", %" PRId64 "},", elm.name.c_str(), elm.value); + } + APPEND_LIT_LN(o, "};"); + } + + // Generate lookup function + 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);", mapName); + APPEND_FMT_LN(o, " if (iter != %s.end()) {", mapName); + 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)); + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } +} + +void HandleInputFile(AppState& state, std::string_view filenameStem, std::string_view source) { + auto tokens = RecordTokens(source); + size_t idx = 0; + +#if CODEGEN_DEBUG_PRINT + printf("BEGIN tokens\n"); + for (auto& token : tokens) { + printf(" token %-32s '%s'\n", STR_LUT_LOOKUP(ClexNames, token.type), token.text.c_str()); + } + printf("END tokens\n"); +#endif + + CodegenInput cgInput; + CodegenOutput cgHeaderOutput; + Utils::ProduceGeneratedHeaderFileHeader(cgHeaderOutput); + CodegenOutput cgSourceOutput; + Utils::ProduceGeneratedSourceFileHeader(cgSourceOutput); + + int currentBraceDepth = 0; + // The current effective namespace, see example + DeclNamespace* currentNamespace = nullptr; + + 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 */ + // } + // } + + while (idx < tokens.size()) { + auto& token = tokens[idx]; + + bool incrementTokenIdx = true; + + switch (token.type) { + case CLEX_id: { + CppKeyword keyword; + { + auto& map = BSTR_LUT_S2V(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; + + while (true) { + if (tokens[idx].type != CLEX_id) { + // TODO better error recovery + printf("[ERROR] invalid syntax for namespace\n"); + break; + } + + currentNamespace = cgInput.AddNamespace(DeclNamespace{ + .container = currentNamespace, + .name = tokens[idx].text, + }); + + if (tokens[idx + 1].text[0] == ':' && + tokens[idx + 2].text[0] == ':') + { + // Skip the two ':' tokens, try parse the next identifier + idx += 3; + } else { + break; + } + } + + nsStack.push_back(NamespaceStackframe{ + .ns = currentNamespace, + .depth = currentBraceDepth, + }); + + goto endIdenCase; + } + + case CKw_Struct: + case CKw_Class: { + auto& idenTok = tokens[idx + 1]; // TODO handle end of list + DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); + goto endIdenCase; + } + + case CKw_Enum: { + // Consume the "enum" keyword + ++idx; + incrementTokenIdx = false; + + DeclEnum enumDecl; + enumDecl.container = currentNamespace; + 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", 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; + } + + auto fullname = Utils::MakeFullName(enumDecl.name, currentNamespace); + cgInput.AddEnum(std::move(fullname), std::move(enumDecl)); + goto endIdenCase; + } + + case CKw_COUNT: break; + } + + CodegenDirective directive; + { + auto& map = BSTR_LUT_S2V(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_ClassInfo: { + // TODO + goto endIdenCase; + } + + case CD_EnumInfo: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + auto& optionsStrMap = BSTR_LUT_S2V(EnumMetaGenOptions); + auto [argList, newIdx] = PeekDirectiveArgumentList(tokens, idx); + if (argList.size() < 1) { + printf("[ERROR] invalid syntax for BRUSSEL_ENUM\n"); + break; // TODO handle this error case gracefully (advance to semicolon?) + } + + auto& enumName = argList[0][0]->text; + auto enumDecl = cgInput.FindEnumByName(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); + + idx = newIdx; + incrementTokenIdx = false; + goto endIdenCase; + } + + case CD_COUNT: break; + } + + endIdenCase: + break; + } + + case CLEX_ext_single_char: + switch (token.text[0]) { + case '{': { + currentBraceDepth++; + CheckBraceDepth(currentBraceDepth); + } break; + + case '}': { + currentBraceDepth--; + CheckBraceDepth(currentBraceDepth); + + if (!nsStack.empty()) { + auto& ns = nsStack.back(); + if (ns.depth == currentBraceDepth) { + nsStack.pop_back(); + + if (!nsStack.empty()) { + currentNamespace = nsStack.back().ns; + } else { + currentNamespace = nullptr; + } + } + } + } break; + } + break; + } + + if (incrementTokenIdx) { + ++idx; + } + } + + if (currentBraceDepth != 0) { + printf("[WARNING] unbalanced brace at end of file."); + } + + Utils::WriteOutputFile(cgHeaderOutput, state.outputDir, filenameStem, ".gh.inl"sv); + Utils::WriteOutputFile(cgSourceOutput, state.outputDir, filenameStem, ".gs.inl"sv); +} + +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[]) { + STR_LUT_INIT(ClexNames); + BSTR_LUT_INIT(CppKeyword); + BSTR_LUT_INIT(CodegenDirective); + BSTR_LUT_INIT(StructMetaGenOptions); + BSTR_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; + + Utils::ProduceGeneratedHeaderFileHeader(state.mainHeaderOutput); + Utils::ProduceGeneratedSourceFileHeader(state.mainSourceOutput); + + // 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); + } + } + + Utils::WriteOutputFile(state.mainHeaderOutput, state.outputDir, "GeneratedCode.hpp"sv); + Utils::WriteOutputFile(state.mainSourceOutput, state.outputDir, "GeneratedCode.cpp"sv); + + return 0; +} diff --git a/buildtools/codegen/tests/examples/TestEnum.hpp.txt b/buildtools/codegen/tests/examples/TestEnum.hpp.txt new file mode 100644 index 0000000..441d97c --- /dev/null +++ b/buildtools/codegen/tests/examples/TestEnum.hpp.txt @@ -0,0 +1,43 @@ +enum MyEnum { + EnumElement1, + EnumElement2, + EnumElement3, +}; +BRUSSEL_ENUM(MyEnum, ToString FromString); + +enum CountedEnumAll { + CEA_Foo, + CEA_Bar, + CEA_COUNT, +}; +BRUSSEL_ENUM(CountedEnumAll, ToString FromString); + +enum CountedEnum { + CE_Foo, + CE_Bar, + CE_FooBar, + CE_COUNT, +}; +BRUSSEL_ENUM(CountedEnum, ToString FromString ExcludeHeuristics); + +namespace MyNamespace { +enum MyNamespacedEnum { + MNE_Foo, + MNE_Bar, +}; +BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); + +namespace details { + enum MyNamespacedEnum { + MNE_Foo, + MNE_Bar, + }; + BRUSSEL_ENUM(MyNamespacedEnum, ToString FromString ExcludeHeuristics); +} +} + +namespace foo::details { +enum Enum { +}; +BRUSSEL_ENUM(Enum, ToString FromString ExcludeHeuristics); +} diff --git a/conanfile.txt b/conanfile.txt index f80978c..2b03fbb 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -5,6 +5,7 @@ assimp/5.2.2 rapidjson/cci.20211112 stb/cci.20210910 robin-hood-hashing/3.11.5 +frozen/1.1.1 [generators] cmake diff --git a/source-codegen-base/MacrosCodegen.hpp b/source-codegen-base/MacrosCodegen.hpp new file mode 100644 index 0000000..6803023 --- /dev/null +++ b/source-codegen-base/MacrosCodegen.hpp @@ -0,0 +1,7 @@ +// NOTE: contents of this file is coupled with buildtools/codegen/ +// when updating, change both sides at the same time + +#pragma once + +#define BRUSSEL_CLASS(name, options) +#define BRUSSEL_ENUM(name, options) diff --git a/source-codegen-base/Metadata.cpp b/source-codegen-base/Metadata.cpp new file mode 100644 index 0000000..ee32054 --- /dev/null +++ b/source-codegen-base/Metadata.cpp @@ -0,0 +1 @@ +#include "Metadata.hpp" diff --git a/source-codegen-base/Metadata.hpp b/source-codegen-base/Metadata.hpp new file mode 100644 index 0000000..a038c15 --- /dev/null +++ b/source-codegen-base/Metadata.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "MacrosCodegen.hpp" +#include "MetadataBase.hpp" diff --git a/source-codegen-base/MetadataBase.cpp b/source-codegen-base/MetadataBase.cpp new file mode 100644 index 0000000..3ccf870 --- /dev/null +++ b/source-codegen-base/MetadataBase.cpp @@ -0,0 +1 @@ +#include "MetadataBase.hpp" diff --git a/source-codegen-base/MetadataBase.hpp b/source-codegen-base/MetadataBase.hpp new file mode 100644 index 0000000..8be668d --- /dev/null +++ b/source-codegen-base/MetadataBase.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <optional> +#include <string_view> + +namespace Metadata { + +template <class TEnum> +std::string_view EnumToString(TEnum value); + +template <class TEnum> +std::optional<TEnum> EnumFromString(std::string_view str); + +} // namespace Metadata diff --git a/source/Enum.hpp b/source-common/Enum.hpp index 5e106fe..8ad75ba 100644 --- a/source/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 { @@ -61,32 +61,32 @@ public: } } - EnumFlags& operator|=(EnumFlags that) const { + EnumFlags& operator|=(EnumFlags that) { mValue |= that.mValue; return *this; } - EnumFlags& operator&=(EnumFlags that) const { + EnumFlags& operator&=(EnumFlags that) { mValue &= that.mValue; return *this; } - EnumFlags& operator^=(EnumFlags that) const { + EnumFlags& operator^=(EnumFlags that) { mValue ^= that.mValue; return *this; } - EnumFlags& operator|=(TEnum e) const { + EnumFlags& operator|=(TEnum e) { mValue |= 1 << static_cast<Underlying>(e); return *this; } - EnumFlags& operator&=(TEnum e) const { + EnumFlags& operator&=(TEnum e) { mValue &= 1 << static_cast<Underlying>(e); return *this; } - EnumFlags& operator^=(TEnum e) const { + EnumFlags& operator^=(TEnum e) { mValue ^= 1 << static_cast<Underlying>(e); return *this; } diff --git a/source-common/LookupTable.hpp b/source-common/LookupTable.hpp new file mode 100644 index 0000000..c50e28e --- /dev/null +++ b/source-common/LookupTable.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include <robin_hood.h> +#include <string_view> + +// BIDI stands for bi-directional +#define BIDI_LUT_DECL(name, aType, aCount, bType, bCount) \ + int gLut_##name##_A2B[aCount]; \ + int gLut_##name##_B2A[bCount]; \ + using name##AType = aType; \ + using name##BType = bType; \ + void InitializeLut##name() +#define BIDI_LUT_MAP_FOR(name) \ + int* lutMappingA2B = gLut_##name##_A2B; \ + int* lutMappingB2A = gLut_##name##_B2A +#define BIDI_LUT_MAP(from, to) \ + lutMappingA2B[from] = to; \ + lutMappingB2A[to] = from +#define BIDI_LUT_INIT(name) InitializeLut##name() +#define BIDI_LUT_A2B_LOOKUP(name, from) (name##BType)(gLut_##name##_A2B[from]) +#define BIDI_LUT_B2A_LOOKUP(name, to) (name##AType)(gLut_##name##_B2A[to]) + +#define STR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutMinVal_##name = enumMinValue; \ + const char* gLut_##name[(int)enumMaxValue - (int)enumMinValue]; \ + void InitializeLut##name() +#define STR_LUT_MAP_FOR(name) \ + const char** lutMapping = gLut_##name; \ + int lutMappingMinValue = kLutMinVal_##name +#define STR_LUT_MAP(value, text) lutMapping[value - lutMappingMinValue] = text +#define STR_LUT_MAP_ENUM(enumValue) STR_LUT_MAP(enumValue, #enumValue) +#define STR_LUT_LOOKUP(name, enumValue) gLut_##name[enumValue - kLutMinVal_##name] +#define STR_LUT_INIT(name) InitializeLut##name() + +// BSTR stands for bi-directional string +#define BSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutMinVal_##name = enumMinValue; \ + const char* gLut_##name##_V2S[(int)enumMaxValue - (int)enumMinValue]; \ + robin_hood::unordered_flat_map<std::string_view, decltype(enumMaxValue)> gLut_##name##_S2V; \ + void InitializeLut##name() +#define BSTR_LUT_MAP_FOR(name) \ + const char** lutMappingV2S = gLut_##name##_V2S; \ + auto& lutMappingS2V = gLut_##name##_S2V; \ + int lutMappingMinValue = kLutMinVal_##name +#define BSTR_LUT_MAP(value, text) \ + lutMappingV2S[value - lutMappingMinValue] = text; \ + lutMappingS2V.insert_or_assign(std::string_view(text), value); +#define BSTR_LUT_MAP_ENUM(enumValue) BSTR_LUT_MAP(enumValue, #enumValue) +#define BSTR_LUT_V2S(name) gLut_##name##_V2S +#define BSTR_LUT_S2V(name) gLut_##name##_S2V +#define BSTR_LUT_V2S_LOOKUP(name, enumValue) gLut_##name##_V2S[enumValue - kLutMinVal_##name] +#define BSTR_LUT_S2V_LOOKUP(name, string) gLut_##name##_S2V.find(std::string_view(text)) +#define BSTR_LUT_INIT(name) InitializeLut##name() diff --git a/source/Macros.hpp b/source-common/Macros.hpp index b5d05fa..a255ada 100644 --- a/source/Macros.hpp +++ b/source-common/Macros.hpp @@ -14,6 +14,8 @@ #define UNUSED(x) (void)x; +#define PRINTF_STRING_VIEW(s) (int)s.size(), s.data() + #if defined(_MSC_VER) # define UNREACHABLE __assume(0) #elif defined(__GNUC__) || defined(__clang__) diff --git a/source/PodVector.hpp b/source-common/PodVector.hpp index 74e99d6..74e99d6 100644 --- a/source/PodVector.hpp +++ b/source-common/PodVector.hpp diff --git a/source/RapidJsonHelper.hpp b/source-common/RapidJsonHelper.hpp index 75cd93a..75cd93a 100644 --- a/source/RapidJsonHelper.hpp +++ b/source-common/RapidJsonHelper.hpp diff --git a/source/RcPtr.hpp b/source-common/RcPtr.hpp index 130b2b2..130b2b2 100644 --- a/source/RcPtr.hpp +++ b/source-common/RcPtr.hpp diff --git a/source/Rect.hpp b/source-common/Rect.hpp index 89d9b01..89d9b01 100644 --- a/source/Rect.hpp +++ b/source-common/Rect.hpp diff --git a/source/ScopeGuard.hpp b/source-common/ScopeGuard.hpp index 28f3385..28f3385 100644 --- a/source/ScopeGuard.hpp +++ b/source-common/ScopeGuard.hpp diff --git a/source/SmallVector.cpp b/source-common/SmallVector.cpp index c38e8a7..c38e8a7 100644 --- a/source/SmallVector.cpp +++ b/source-common/SmallVector.cpp diff --git a/source/SmallVector.hpp b/source-common/SmallVector.hpp index e33a25d..e33a25d 100644 --- a/source/SmallVector.hpp +++ b/source-common/SmallVector.hpp diff --git a/source/stb_implementation.c b/source-common/StbImplementations.c index 078ca5d..73bbc2a 100644 --- a/source/stb_implementation.c +++ b/source-common/StbImplementations.c @@ -9,3 +9,6 @@ #define STB_SPRINTF_IMPLEMENTATION #include <stb_sprintf.h> + +#define STB_C_LEXER_IMPLEMENTATION +#include <stb_c_lexer.h> diff --git a/source/TypeTraits.hpp b/source-common/TypeTraits.hpp index cca9a1f..cca9a1f 100644 --- a/source/TypeTraits.hpp +++ b/source-common/TypeTraits.hpp diff --git a/source/Uid.cpp b/source-common/Uid.cpp index 1930cd8..1930cd8 100644 --- a/source/Uid.cpp +++ b/source-common/Uid.cpp diff --git a/source/Uid.hpp b/source-common/Uid.hpp index f58129c..f58129c 100644 --- a/source/Uid.hpp +++ b/source-common/Uid.hpp diff --git a/source/Utils.cpp b/source-common/Utils.cpp index 53b3863..53b3863 100644 --- a/source/Utils.cpp +++ b/source-common/Utils.cpp diff --git a/source/Utils.hpp b/source-common/Utils.hpp index 9f28aad..9f28aad 100644 --- a/source/Utils.hpp +++ b/source-common/Utils.hpp diff --git a/source/YCombinator.hpp b/source-common/YCombinator.hpp index b1d2350..b1d2350 100644 --- a/source/YCombinator.hpp +++ b/source-common/YCombinator.hpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 6ca2cd5..abb724b 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -22,18 +22,14 @@ PRIVATE Renderer.cpp SceneThings.cpp Shader.cpp - SmallVector.cpp Sprite.cpp Texture.cpp - Uid.cpp VertexIndex.cpp World.cpp ) set(ProjectBrussel_SINGLE_UNIT_SRC - stb_implementation.c main.cpp - Utils.cpp # May include platform headers ) target_sources(${PROJECT_NAME} PRIVATE ${ProjectBrussel_SINGLE_UNIT_SRC}) set_source_files_properties(${ProjectBrussel_SINGLE_UNIT_SRC} diff --git a/source/GraphicsTags.cpp b/source/GraphicsTags.cpp index b389acf..522a58f 100644 --- a/source/GraphicsTags.cpp +++ b/source/GraphicsTags.cpp @@ -306,3 +306,5 @@ GLenum Tags::FindGLType(std::string_view name) { return GL_NONE; } } + +#include <generated/GraphicsTags.gs.inl> diff --git a/source/GraphicsTags.hpp b/source/GraphicsTags.hpp index 34c0885..f83b99c 100644 --- a/source/GraphicsTags.hpp +++ b/source/GraphicsTags.hpp @@ -5,6 +5,8 @@ #include <string> #include <string_view> +#include <MacrosCodegen.hpp> + namespace Tags { /// Vertex element semantics, used to identify the meaning of vertex buffer contents enum VertexElementSemantic { @@ -32,6 +34,7 @@ enum VertexElementSemantic { VES_Generic, VES_COUNT, }; +BRUSSEL_ENUM(VertexElementSemantic, ToString FromString ExcludeHeuristics); std::string_view NameOf(VertexElementSemantic semantic); VertexElementSemantic FindVertexElementSemantic(std::string_view name); @@ -72,6 +75,8 @@ enum VertexElementType { VET_Ushort4Norm, VET_NORM_END = VET_Ushort4Norm, }; +// TODO this enum isn't continuous, not supported yet +// BRUSSEL_ENUM(VertexElementType, ToString FromString ExcludeHeuristics); int SizeOf(VertexElementType type); int VectorLenOf(VertexElementType type); @@ -96,3 +101,5 @@ GLenum FindGLType(std::string_view name); constexpr GLuint kInvalidLocation = std::numeric_limits<GLuint>::max(); } // namespace Tags + +#include <generated/GraphicsTags.gh.inl> |