aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-05-30 15:56:29 -0700
committerrtk0c <[email protected]>2022-05-30 15:56:29 -0700
commit80afa67d2b9f1c0605696a3fd69058544fe12fe4 (patch)
tree3dfe2a6f45e4f5ca8ad534baf06d76d9c558333b
parent0d92ecfdbfc875a099d9e83714b3a2209668fca5 (diff)
parent7d8bca09b3c4bf1418e758bd3bd0d6f85672153e (diff)
Changeset: 53
-rw-r--r--CMakeLists.txt99
-rw-r--r--README.md9
-rw-r--r--buildtools/cmake/RTTI.cmake31
-rw-r--r--buildtools/codegen/CodegenConfig.hpp11
-rw-r--r--buildtools/codegen/CodegenDecl.cpp49
-rw-r--r--buildtools/codegen/CodegenDecl.hpp74
-rw-r--r--buildtools/codegen/CodegenInput.inl69
-rw-r--r--buildtools/codegen/CodegenMacros.hpp30
-rw-r--r--buildtools/codegen/CodegenOutput.inl76
-rw-r--r--buildtools/codegen/CodegenUtils.inl106
-rw-r--r--buildtools/codegen/README.md5
-rw-r--r--buildtools/codegen/main.cpp757
-rw-r--r--buildtools/codegen/tests/examples/TestEnum.hpp.txt43
-rw-r--r--conanfile.txt1
-rw-r--r--source-codegen-base/MacrosCodegen.hpp7
-rw-r--r--source-codegen-base/Metadata.cpp1
-rw-r--r--source-codegen-base/Metadata.hpp4
-rw-r--r--source-codegen-base/MetadataBase.cpp1
-rw-r--r--source-codegen-base/MetadataBase.hpp14
-rw-r--r--source-common/Enum.hpp (renamed from source/Enum.hpp)14
-rw-r--r--source-common/LookupTable.hpp53
-rw-r--r--source-common/Macros.hpp (renamed from source/Macros.hpp)2
-rw-r--r--source-common/PodVector.hpp (renamed from source/PodVector.hpp)0
-rw-r--r--source-common/RapidJsonHelper.hpp (renamed from source/RapidJsonHelper.hpp)0
-rw-r--r--source-common/RcPtr.hpp (renamed from source/RcPtr.hpp)0
-rw-r--r--source-common/Rect.hpp (renamed from source/Rect.hpp)0
-rw-r--r--source-common/ScopeGuard.hpp (renamed from source/ScopeGuard.hpp)0
-rw-r--r--source-common/SmallVector.cpp (renamed from source/SmallVector.cpp)0
-rw-r--r--source-common/SmallVector.hpp (renamed from source/SmallVector.hpp)0
-rw-r--r--source-common/StbImplementations.c (renamed from source/stb_implementation.c)3
-rw-r--r--source-common/TypeTraits.hpp (renamed from source/TypeTraits.hpp)0
-rw-r--r--source-common/Uid.cpp (renamed from source/Uid.cpp)0
-rw-r--r--source-common/Uid.hpp (renamed from source/Uid.hpp)0
-rw-r--r--source-common/Utils.cpp (renamed from source/Utils.cpp)0
-rw-r--r--source-common/Utils.hpp (renamed from source/Utils.hpp)0
-rw-r--r--source-common/YCombinator.hpp (renamed from source/YCombinator.hpp)0
-rw-r--r--source/CMakeLists.txt4
-rw-r--r--source/GraphicsTags.cpp2
-rw-r--r--source/GraphicsTags.hpp7
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})
diff --git a/README.md b/README.md
index dc0b4c1..5caf9f7 100644
--- a/README.md
+++ b/README.md
@@ -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>