aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/10-common/Color.hpp (renamed from source/Color.hpp)0
-rw-r--r--source/10-common/Enum.hpp103
-rw-r--r--source/10-common/LookupTable.hpp53
-rw-r--r--source/10-common/Macros.hpp31
-rw-r--r--source/10-common/PodVector.hpp297
-rw-r--r--source/10-common/RapidJsonHelper.hpp110
-rw-r--r--source/10-common/RcPtr.hpp120
-rw-r--r--source/10-common/Rect.hpp164
-rw-r--r--source/10-common/ScopeGuard.hpp60
-rw-r--r--source/10-common/SmallVector.cpp145
-rw-r--r--source/10-common/SmallVector.hpp1332
-rw-r--r--source/10-common/StbImplementations.c14
-rw-r--r--source/10-common/TypeTraits.hpp19
-rw-r--r--source/10-common/Uid.cpp58
-rw-r--r--source/10-common/Uid.hpp42
-rw-r--r--source/10-common/Utils.cpp87
-rw-r--r--source/10-common/Utils.hpp61
-rw-r--r--source/10-common/YCombinator.hpp14
-rw-r--r--source/20-codegen-compiler/CodegenConfig.hpp11
-rw-r--r--source/20-codegen-compiler/CodegenDecl.cpp49
-rw-r--r--source/20-codegen-compiler/CodegenDecl.hpp74
-rw-r--r--source/20-codegen-compiler/CodegenInput.inl69
-rw-r--r--source/20-codegen-compiler/CodegenMacros.hpp30
-rw-r--r--source/20-codegen-compiler/CodegenOutput.inl76
-rw-r--r--source/20-codegen-compiler/CodegenUtils.inl106
-rw-r--r--source/20-codegen-compiler/main.cpp761
-rw-r--r--source/20-codegen-compiler/test/examples/TestEnum.hpp.txt43
-rw-r--r--source/20-codegen-runtime/MacrosCodegen.hpp7
-rw-r--r--source/20-codegen-runtime/Metadata.cpp1
-rw-r--r--source/20-codegen-runtime/Metadata.hpp4
-rw-r--r--source/20-codegen-runtime/MetadataBase.cpp1
-rw-r--r--source/20-codegen-runtime/MetadataBase.hpp14
-rw-r--r--source/30-game/App.cpp (renamed from source/App.cpp)0
-rw-r--r--source/30-game/App.hpp (renamed from source/App.hpp)0
-rw-r--r--source/30-game/AppConfig.hpp (renamed from source/AppConfig.hpp)0
-rw-r--r--source/30-game/Camera.cpp (renamed from source/Camera.cpp)0
-rw-r--r--source/30-game/Camera.hpp (renamed from source/Camera.hpp)0
-rw-r--r--source/30-game/CommonVertexIndex.cpp (renamed from source/CommonVertexIndex.cpp)0
-rw-r--r--source/30-game/CommonVertexIndex.hpp (renamed from source/CommonVertexIndex.hpp)0
-rw-r--r--source/30-game/EditorAccessories.cpp (renamed from source/EditorAccessories.cpp)0
-rw-r--r--source/30-game/EditorAccessories.hpp (renamed from source/EditorAccessories.hpp)0
-rw-r--r--source/30-game/EditorAttachment.hpp (renamed from source/EditorAttachment.hpp)0
-rw-r--r--source/30-game/EditorAttachmentImpl.cpp (renamed from source/EditorAttachmentImpl.cpp)0
-rw-r--r--source/30-game/EditorAttachmentImpl.hpp (renamed from source/EditorAttachmentImpl.hpp)0
-rw-r--r--source/30-game/EditorCommandPalette.cpp (renamed from source/EditorCommandPalette.cpp)0
-rw-r--r--source/30-game/EditorCommandPalette.hpp (renamed from source/EditorCommandPalette.hpp)0
-rw-r--r--source/30-game/EditorCore.hpp (renamed from source/EditorCore.hpp)0
-rw-r--r--source/30-game/EditorCorePrivate.cpp (renamed from source/EditorCorePrivate.cpp)0
-rw-r--r--source/30-game/EditorCorePrivate.hpp (renamed from source/EditorCorePrivate.hpp)0
-rw-r--r--source/30-game/EditorGuizmo.cpp (renamed from source/EditorGuizmo.cpp)0
-rw-r--r--source/30-game/EditorGuizmo.hpp (renamed from source/EditorGuizmo.hpp)0
-rw-r--r--source/30-game/EditorNotification.cpp (renamed from source/EditorNotification.cpp)0
-rw-r--r--source/30-game/EditorNotification.hpp (renamed from source/EditorNotification.hpp)0
-rw-r--r--source/30-game/EditorUtils.cpp (renamed from source/EditorUtils.cpp)0
-rw-r--r--source/30-game/EditorUtils.hpp (renamed from source/EditorUtils.hpp)0
-rw-r--r--source/30-game/FuzzyMatch.cpp (renamed from source/FuzzyMatch.cpp)0
-rw-r--r--source/30-game/FuzzyMatch.hpp (renamed from source/FuzzyMatch.hpp)0
-rw-r--r--source/30-game/GameObject.cpp (renamed from source/GameObject.cpp)0
-rw-r--r--source/30-game/GameObject.hpp (renamed from source/GameObject.hpp)0
-rw-r--r--source/30-game/GraphicsTags.cpp (renamed from source/GraphicsTags.cpp)0
-rw-r--r--source/30-game/GraphicsTags.hpp (renamed from source/GraphicsTags.hpp)0
-rw-r--r--source/30-game/Image.cpp (renamed from source/Image.cpp)0
-rw-r--r--source/30-game/Image.hpp (renamed from source/Image.hpp)0
-rw-r--r--source/30-game/Ires.cpp (renamed from source/Ires.cpp)0
-rw-r--r--source/30-game/Ires.hpp (renamed from source/Ires.hpp)0
-rw-r--r--source/30-game/Level.cpp (renamed from source/Level.cpp)0
-rw-r--r--source/30-game/Level.hpp (renamed from source/Level.hpp)0
-rw-r--r--source/30-game/Material.cpp (renamed from source/Material.cpp)0
-rw-r--r--source/30-game/Material.hpp (renamed from source/Material.hpp)0
-rw-r--r--source/30-game/Mesh.cpp (renamed from source/Mesh.cpp)0
-rw-r--r--source/30-game/Mesh.hpp (renamed from source/Mesh.hpp)0
-rw-r--r--source/30-game/Player.cpp (renamed from source/Player.cpp)0
-rw-r--r--source/30-game/Player.hpp (renamed from source/Player.hpp)0
-rw-r--r--source/30-game/Renderer.cpp (renamed from source/Renderer.cpp)0
-rw-r--r--source/30-game/Renderer.hpp (renamed from source/Renderer.hpp)0
-rw-r--r--source/30-game/SceneThings.cpp (renamed from source/SceneThings.cpp)0
-rw-r--r--source/30-game/SceneThings.hpp (renamed from source/SceneThings.hpp)0
-rw-r--r--source/30-game/Shader.cpp (renamed from source/Shader.cpp)0
-rw-r--r--source/30-game/Shader.hpp (renamed from source/Shader.hpp)0
-rw-r--r--source/30-game/Sprite.cpp (renamed from source/Sprite.cpp)0
-rw-r--r--source/30-game/Sprite.hpp (renamed from source/Sprite.hpp)0
-rw-r--r--source/30-game/Texture.cpp (renamed from source/Texture.cpp)0
-rw-r--r--source/30-game/Texture.hpp (renamed from source/Texture.hpp)0
-rw-r--r--source/30-game/VertexIndex.cpp (renamed from source/VertexIndex.cpp)0
-rw-r--r--source/30-game/VertexIndex.hpp (renamed from source/VertexIndex.hpp)0
-rw-r--r--source/30-game/World.cpp (renamed from source/World.cpp)0
-rw-r--r--source/30-game/World.hpp (renamed from source/World.hpp)0
-rw-r--r--source/30-game/main.cpp (renamed from source/main.cpp)0
88 files changed, 3956 insertions, 0 deletions
diff --git a/source/Color.hpp b/source/10-common/Color.hpp
index ef0c5a9..ef0c5a9 100644
--- a/source/Color.hpp
+++ b/source/10-common/Color.hpp
diff --git a/source/10-common/Enum.hpp b/source/10-common/Enum.hpp
new file mode 100644
index 0000000..8ad75ba
--- /dev/null
+++ b/source/10-common/Enum.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <initializer_list>
+#include <type_traits>
+
+template <class TEnum>
+class EnumFlags {
+public:
+ using Enum = TEnum;
+ using Underlying = std::underlying_type_t<TEnum>;
+
+private:
+ Underlying mValue;
+
+public:
+ EnumFlags()
+ : mValue{ 0 } {
+ }
+
+ EnumFlags(TEnum e)
+ : mValue{ static_cast<Underlying>(1) << static_cast<Underlying>(e) } {
+ }
+
+ bool IsSet(EnumFlags mask) const {
+ return (mValue & mask.mValue) == mask.mValue;
+ }
+
+ bool IsSet(std::initializer_list<TEnum> enums) {
+ EnumFlags flags;
+ for (auto& e : enums) {
+ flags.mValue |= static_cast<Underlying>(e);
+ }
+ return IsSet(flags);
+ }
+
+ bool IsSetExclusive(EnumFlags mask) const {
+ return mValue == mask.mValue;
+ }
+
+ bool IsSetExclusive(std::initializer_list<TEnum> enums) {
+ EnumFlags flags;
+ for (auto& e : enums) {
+ flags.mValue |= static_cast<Underlying>(e);
+ }
+ return IsSetExclusive(flags);
+ }
+
+ void SetOn(EnumFlags mask) {
+ mValue |= mask.mValue;
+ }
+
+ void SetOff(EnumFlags mask) {
+ mValue &= ~mask.mValue;
+ }
+
+ void Set(EnumFlags mask, bool enabled) {
+ if (enabled) {
+ SetOn(mask);
+ } else {
+ SetOff(mask);
+ }
+ }
+
+ EnumFlags& operator|=(EnumFlags that) {
+ mValue |= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator&=(EnumFlags that) {
+ mValue &= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator^=(EnumFlags that) {
+ mValue ^= that.mValue;
+ return *this;
+ }
+
+ EnumFlags& operator|=(TEnum e) {
+ mValue |= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags& operator&=(TEnum e) {
+ mValue &= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags& operator^=(TEnum e) {
+ mValue ^= 1 << static_cast<Underlying>(e);
+ return *this;
+ }
+
+ EnumFlags operator|(EnumFlags that) const { return EnumFlags(mValue | that.mValue); }
+ EnumFlags operator&(EnumFlags that) const { return EnumFlags(mValue & that.mValue); }
+ EnumFlags operator^(EnumFlags that) const { return EnumFlags(mValue ^ that.mValue); }
+
+ EnumFlags operator|(TEnum e) const { return EnumFlags(mValue | 1 << static_cast<Underlying>(e)); }
+ EnumFlags operator&(TEnum e) const { return EnumFlags(mValue & 1 << static_cast<Underlying>(e)); }
+ EnumFlags operator^(TEnum e) const { return EnumFlags(mValue ^ 1 << static_cast<Underlying>(e)); }
+
+ EnumFlags operator~() const { return EnumFlags(~mValue); }
+};
diff --git a/source/10-common/LookupTable.hpp b/source/10-common/LookupTable.hpp
new file mode 100644
index 0000000..c50e28e
--- /dev/null
+++ b/source/10-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/10-common/Macros.hpp b/source/10-common/Macros.hpp
new file mode 100644
index 0000000..a255ada
--- /dev/null
+++ b/source/10-common/Macros.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#define STRINGIFY_IMPL(text) #text
+#define STRINGIFY(text) STRINGIFY_IMPL(text)
+
+#define CONCAT_IMPL(a, b) a##b
+#define CONCAT(a, b) CONCAT_IMPL(a, b)
+#define CONCAT_3(a, b, c) CONCAT(a, CONCAT(b, c))
+#define CONCAT_4(a, b, c, d) CONCAT(CONCAT(a, b), CONCAT(c, d))
+
+#define UNIQUE_NAME(prefix) CONCAT(prefix, __COUNTER__)
+#define UNIQUE_NAME_LINE(prefix) CONCAT(prefix, __LINE__)
+#define DISCARD UNIQUE_NAME(_discard)
+
+#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__)
+# define UNREACHABLE __builtin_unreachable()
+#else
+# define UNREACHABLE
+#endif
+
+#if _WIN32
+# define PLATFORM_PATH_STR "%ls"
+#else
+# define PLATFORM_PATH_STR "%s"
+#endif
diff --git a/source/10-common/PodVector.hpp b/source/10-common/PodVector.hpp
new file mode 100644
index 0000000..74e99d6
--- /dev/null
+++ b/source/10-common/PodVector.hpp
@@ -0,0 +1,297 @@
+// File adapted from dear-imgui's ImVector, implemented in https://github.com/ocornut/imgUI/blob/master/imgui.h
+#pragma once
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <span>
+
+template <class T>
+class PodVector {
+public:
+ using value_type = T;
+ using iterator = value_type*;
+ using const_iterator = const value_type*;
+
+private:
+ int mSize;
+ int mCapacity;
+ T* mData;
+
+public:
+ PodVector() {
+ mSize = mCapacity = 0;
+ mData = nullptr;
+ }
+
+ PodVector(const PodVector<T>& src) {
+ mSize = mCapacity = 0;
+ mData = nullptr;
+ operator=(src);
+ }
+
+ PodVector<T>& operator=(const PodVector<T>& src) {
+ clear();
+ resize(src.mSize);
+ std::memcpy(mData, src.mData, (size_t)mSize * sizeof(T));
+ return *this;
+ }
+
+ PodVector(PodVector&& src) {
+ mSize = src.mSize;
+ mCapacity = src.mCapacity;
+ mData = src.mData;
+
+ src.mSize = src.mCapacity = 0;
+ src.mData = nullptr;
+ }
+
+ PodVector& operator=(PodVector&& src) {
+ if (this != &src) {
+ std::free(mData);
+
+ mSize = src.mSize;
+ mCapacity = src.mCapacity;
+ mData = src.mData;
+
+ src.mSize = src.mCapacity = 0;
+ src.mData = nullptr;
+ }
+ return *this;
+ }
+
+ ~PodVector() {
+ std::free(mData);
+ }
+
+ bool empty() const { return mSize == 0; }
+ int size() const { return mSize; }
+ int size_in_bytes() const { return mSize * (int)sizeof(T); }
+ int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); }
+ int capacity() const { return mCapacity; }
+
+ T& operator[](int i) {
+ assert(i >= 0 && i < mSize);
+ return mData[i];
+ }
+
+ const T& operator[](int i) const {
+ assert(i >= 0 && i < mSize);
+ return mData[i];
+ }
+
+ void clear() {
+ if (mData) {
+ mSize = mCapacity = 0;
+ std::free(mData);
+ mData = nullptr;
+ }
+ }
+
+ T* begin() { return mData; }
+ const T* begin() const { return mData; }
+ T* end() { return mData + mSize; }
+ const T* end() const { return mData + mSize; }
+
+ T* data() { return mData; }
+
+ T& front() {
+ assert(mSize > 0);
+ return mData[0];
+ }
+
+ const T& front() const {
+ assert(mSize > 0);
+ return mData[0];
+ }
+
+ T& back() {
+ assert(mSize > 0);
+ return mData[mSize - 1];
+ }
+
+ const T& back() const {
+ assert(mSize > 0);
+ return mData[mSize - 1];
+ }
+
+ void swap(PodVector<T>& rhs) {
+ int rhs_size = rhs.mSize;
+ rhs.mSize = mSize;
+ mSize = rhs_size;
+ int rhs_cap = rhs.mCapacity;
+ rhs.mCapacity = mCapacity;
+ mCapacity = rhs_cap;
+ T* rhs_mDataTmp = rhs.mData;
+ rhs.mData = mData;
+ mData = rhs_mDataTmp;
+ }
+
+ int grow_capacity(int sz) const {
+ int newCapacity = mCapacity ? (mCapacity + mCapacity / 2) : 8;
+ return newCapacity > sz ? newCapacity : sz;
+ }
+
+ void resize(int new_size) {
+ if (new_size > mCapacity) reserve(grow_capacity(new_size));
+ mSize = new_size;
+ }
+
+ void resize_more(int size) {
+ resize(mSize + size);
+ }
+
+ void resize(int new_size, const T& v) {
+ if (new_size > mCapacity) reserve(grow_capacity(new_size));
+ if (new_size > mSize) {
+ for (int n = mSize; n < new_size; n++) {
+ std::memcpy(&mData[n], &v, sizeof(v));
+ }
+ }
+ mSize = new_size;
+ }
+
+ void resize_more(int size, const T& v) {
+ resize(mSize + size, v);
+ }
+
+ void shrink(int new_size) {
+ assert(new_size <= mSize);
+ mSize = new_size;
+ }
+
+ /// Resize a vector to a smaller mSize, guaranteed not to cause a reallocation
+ void reserve(int newCapacity) {
+ if (newCapacity <= mCapacity) return;
+ auto tmp = (T*)std::malloc((size_t)newCapacity * sizeof(T));
+ if (mData) {
+ std::memcpy(tmp, mData, (size_t)mSize * sizeof(T));
+ std::free(mData);
+ }
+ mData = tmp;
+ mCapacity = newCapacity;
+ }
+
+ void reserve_more(int size) {
+ reserve(mSize + size);
+ }
+
+ /// NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the PodVector data itself! e.g. v.push_back(v[10]) is forbidden.
+ void push_back(const T& v) {
+ if (mSize == mCapacity) reserve(grow_capacity(mSize + 1));
+ std::memcpy(&mData[mSize], &v, sizeof(v));
+ mSize++;
+ }
+
+ void pop_back() {
+ assert(mSize > 0);
+ mSize--;
+ }
+
+ void push_front(const T& v) {
+ if (mSize == 0) {
+ push_back(v);
+ } else {
+ insert(mData, v);
+ }
+ }
+
+ T* erase(const T* it) {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ std::memmove(mData + off, mData + off + 1, ((size_t)mSize - (size_t)off - 1) * sizeof(T));
+ mSize--;
+ return mData + off;
+ }
+
+ T* erase(const T* it, const T* it_last) {
+ assert(it >= mData && it < mData + mSize && it_last > it && it_last <= mData + mSize);
+ const ptrdiff_t count = it_last - it;
+ const ptrdiff_t off = it - mData;
+ std::memmove(mData + off, mData + off + count, ((size_t)mSize - (size_t)off - count) * sizeof(T));
+ mSize -= (int)count;
+ return mData + off;
+ }
+
+ T* erase_unsorted(const T* it) {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ if (it < mData + mSize - 1) std::memcpy(mData + off, mData + mSize - 1, sizeof(T));
+ mSize--;
+ return mData + off;
+ }
+
+ T* insert(const T* it, const T& v) {
+ assert(it >= mData && it <= mData + mSize);
+ const ptrdiff_t off = it - mData;
+ if (mSize == mCapacity) reserve(grow_capacity(mSize + 1));
+ if (off < (int)mSize) std::memmove(mData + off + 1, mData + off, ((size_t)mSize - (size_t)off) * sizeof(T));
+ std::memcpy(&mData[off], &v, sizeof(v));
+ mSize++;
+ return mData + off;
+ }
+
+ bool contains(const T& v) const {
+ const T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd) {
+ if (*data++ == v) return true;
+ }
+ return false;
+ }
+
+ T* find(const T& v) {
+ T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd)
+ if (*data == v)
+ break;
+ else
+ ++data;
+ return data;
+ }
+
+ const T* find(const T& v) const {
+ const T* data = mData;
+ const T* dataEnd = mData + mSize;
+ while (data < dataEnd)
+ if (*data == v)
+ break;
+ else
+ ++data;
+ return data;
+ }
+
+ bool find_erase(const T& v) {
+ const T* it = find(v);
+ if (it < mData + mSize) {
+ erase(it);
+ return true;
+ }
+ return false;
+ }
+
+ bool find_erase_unsorted(const T& v) {
+ const T* it = find(v);
+ if (it < mData + mSize) {
+ erase_unsorted(it);
+ return true;
+ }
+ return false;
+ }
+
+ int index_from_ptr(const T* it) const {
+ assert(it >= mData && it < mData + mSize);
+ const ptrdiff_t off = it - mData;
+ return (int)off;
+ }
+
+ // Custom utility functions
+
+ std::span<T> as_span() { return { mData, (size_t)mSize }; }
+ std::span<uint8_t> as_data_span() { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; }
+ std::span<const T> as_span() const { return { mData, (size_t)mSize }; }
+ std::span<const uint8_t> as_data_span() const { return { (uint8_t*)mData, (size_t)mSize * sizeof(T) }; }
+};
diff --git a/source/10-common/RapidJsonHelper.hpp b/source/10-common/RapidJsonHelper.hpp
new file mode 100644
index 0000000..75cd93a
--- /dev/null
+++ b/source/10-common/RapidJsonHelper.hpp
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <rapidjson/document.h>
+#include <cstring>
+#include <string>
+#include <string_view>
+
+#define BRUSSEL_JSON_GET(object, name, type, out, failAction) \
+ { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) failAction; \
+ auto& value = it->value; \
+ if (!value.Is<type>()) failAction; \
+ (out) = value.Get<type>(); \
+ }
+
+#define BRUSSEL_JSON_GET_DEFAULT(object, name, type, out, theDefault) \
+ do { \
+ auto it = (object).FindMember(name); \
+ if (it == (object).MemberEnd()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ auto& value = it->value; \
+ if (!value.Is<type>()) { \
+ (out) = theDefault; \
+ break; \
+ } \
+ (out) = value.Get<type>(); \
+ } while (0);
+
+namespace rapidjson {
+
+inline const Value* GetProperty(const Value& value, std::string_view name) {
+ for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+ if (it->name.GetStringLength() != name.size()) continue;
+ if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue;
+
+ return &it->value;
+ }
+ return nullptr;
+}
+
+inline const Value* GetProperty(const Value& value, Type type, std::string_view name) {
+ for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+ if (it->name.GetStringLength() != name.size()) continue;
+ if (it->value.GetType() != type) continue;
+ if (std::memcmp(it->name.GetString(), name.data(), name.size())) continue;
+
+ return &it->value;
+ }
+ return nullptr;
+}
+
+inline std::string_view AsStringView(const Value& value) {
+ return std::string_view(value.GetString(), value.GetStringLength());
+}
+
+inline std::string_view AsStringView(const GenericStringRef<char>& strRef) {
+ return std::string_view(strRef.s, strRef.length);
+}
+
+inline std::string AsString(const Value& value) {
+ return std::string(value.GetString(), value.GetStringLength());
+}
+
+inline std::string AsString(const GenericStringRef<char>& strRef) {
+ return std::string(strRef.s, strRef.length);
+}
+
+// RapidJson itself already provides std::string and const char* overloads
+inline GenericStringRef<char> StringRef(std::string_view str) {
+ return GenericStringRef<char>(
+ str.data() ? str.data() : "",
+ str.size());
+}
+
+template <class TIter, class TSentienl>
+rapidjson::Value WriteVectorPrimitives(rapidjson::Document& root, TIter begin, TSentienl end) {
+ using TElement = typename TIter::value_type;
+
+ rapidjson::Value list;
+ while (begin != end) {
+ if constexpr (std::is_same_v<TElement, std::string>) {
+ auto& elm = *begin;
+ list.PushBack(rapidjson::Value(elm.c_str(), elm.size()), root.GetAllocator());
+ } else {
+ list.PushBack(*begin, root.GetAllocator());
+ }
+ ++begin;
+ }
+ return list;
+}
+
+template <class TContainer>
+bool ReadVectorPrimitives(const rapidjson::Value& value, TContainer& list) {
+ using TElement = typename TContainer::value_type;
+
+ if (!value.IsArray()) return false;
+
+ list.reserve(value.Size());
+ for (auto& elm : value.GetArray()) {
+ if (!elm.Is<TElement>()) return {};
+ list.push_back(elm.Get<TElement>());
+ }
+
+ return true;
+}
+
+} // namespace rapidjson
diff --git a/source/10-common/RcPtr.hpp b/source/10-common/RcPtr.hpp
new file mode 100644
index 0000000..130b2b2
--- /dev/null
+++ b/source/10-common/RcPtr.hpp
@@ -0,0 +1,120 @@
+#pragma once
+
+#include "Macros.hpp"
+#include "TypeTraits.hpp"
+
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <type_traits>
+
+class RefCounted {
+public:
+ // DO NOT MODIFY this field, unless explicitly documented the use
+ uint32_t refCount = 0;
+ uint32_t weakCount = 0; // TODO implement
+};
+
+template <class T, class TDeleter = DefaultDeleter<T>>
+class RcPtr : TDeleter {
+private:
+ static_assert(std::is_base_of_v<RefCounted, T>);
+ T* mPtr;
+
+public:
+ RcPtr()
+ : mPtr{ nullptr } {
+ }
+
+ explicit RcPtr(T* ptr)
+ : mPtr{ ptr } {
+ if (ptr) {
+ ++ptr->RefCounted::refCount;
+ }
+ }
+
+ ~RcPtr() {
+ CleanUp();
+ }
+
+ void Attach(T* ptr) {
+ CleanUp();
+ mPtr = ptr;
+ if (ptr) {
+ ++ptr->RefCounted::refCount;
+ }
+ }
+
+ void Detatch() {
+ CleanUp();
+ mPtr = nullptr;
+ }
+
+ RcPtr(const RcPtr& that)
+ : mPtr{ that.mPtr } {
+ if (mPtr) {
+ ++mPtr->RefCounted::refCount;
+ }
+ }
+
+ RcPtr& operator=(const RcPtr& that) {
+ CleanUp();
+ mPtr = that.mPtr;
+ if (mPtr) {
+ ++mPtr->RefCounted::refCount;
+ }
+ return *this;
+ }
+
+ RcPtr(RcPtr&& that)
+ : mPtr{ that.mPtr } {
+ that.mPtr = nullptr;
+ }
+
+ RcPtr& operator=(RcPtr&& that) {
+ CleanUp();
+ mPtr = that.mPtr;
+ that.mPtr = nullptr;
+ return *this;
+ }
+
+ template <class TBase>
+ requires std::is_base_of_v<TBase, T>
+ operator RcPtr<TBase>() const {
+ return RcPtr<TBase>(mPtr);
+ }
+
+ bool operator==(std::nullptr_t ptr) const {
+ return mPtr == nullptr;
+ }
+
+ bool operator==(const T* ptr) const {
+ return mPtr == ptr;
+ }
+
+ bool operator==(T* ptr) const {
+ return mPtr == ptr;
+ }
+
+ template <class TThat>
+ bool operator==(const RcPtr<TThat>& ptr) const {
+ return mPtr == ptr.Get();
+ }
+
+ T* Get() const {
+ return mPtr;
+ }
+
+ T& operator*() const { return *mPtr; }
+ T* operator->() const { return mPtr; }
+
+private:
+ void CleanUp() {
+ if (mPtr) {
+ --mPtr->RefCounted::refCount;
+ if (mPtr->RefCounted::refCount == 0) {
+ TDeleter::operator()(mPtr);
+ }
+ }
+ }
+};
diff --git a/source/10-common/Rect.hpp b/source/10-common/Rect.hpp
new file mode 100644
index 0000000..89d9b01
--- /dev/null
+++ b/source/10-common/Rect.hpp
@@ -0,0 +1,164 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+/// Rect is a rectangle representation based on a point and a dimensions, in television coordinate space
+/// (x increases from left to right, y increases from top to bottom).
+template <class T>
+class Rect {
+public:
+ using ScalarType = T;
+ using VectorType = glm::vec<2, T>;
+
+public:
+ T x;
+ T y;
+ T width;
+ T height;
+
+public:
+ Rect()
+ : x{ 0 }, y{ 0 }, width{ 0 }, height{ 0 } {
+ }
+
+ Rect(T x, T y, T width, T height)
+ : x{ x }, y{ y }, width{ width }, height{ height } {
+ }
+
+ Rect(VectorType pos, VectorType size)
+ : x{ pos.x }
+ , y{ pos.y }
+ , width{ size.x }
+ , height{ size.y } {
+ }
+
+ T x0() const { return x; }
+ T y0() const { return y; }
+ T x1() const { return x + width; }
+ T y1() const { return y + height; }
+
+ VectorType TopLeft() const {
+ return VectorType{ x, y };
+ }
+
+ VectorType TopRight() const {
+ return VectorType{ x + width, y };
+ }
+
+ VectorType BottomLeft() const {
+ return VectorType{ x, y + height };
+ }
+
+ VectorType BottomRight() const {
+ return VectorType{ x + width, y + height };
+ }
+
+ VectorType Center() const {
+ return TopLeft() + VectorType{ width / 2, height / 2 };
+ }
+
+ VectorType Dimensions() const {
+ return VectorType{ width, height };
+ }
+
+ VectorType Extents() const {
+ return VectorType{ width / 2, height / 2 };
+ }
+
+ /// Assumes `bySize * 2` is smaller than both `width` and `height` (does not produce a negative-dimension rectangle).
+ Rect Shrink(T bySize) const {
+ T two = bySize * 2;
+ return Rect{ x + bySize, y + bySize, width - two, height - two };
+ }
+
+ Rect Shrink(T left, T top, T right, T bottom) const {
+ return Rect{
+ x + left,
+ y + top,
+ width - left - right,
+ height - top - bottom,
+ };
+ }
+
+ Rect Expand(T bySize) const {
+ T two = bySize * 2;
+ return Rect{ x - bySize, y - bySize, width + two, height + two };
+ }
+
+ Rect Expand(T left, T top, T right, T bottom) const {
+ return Rect{
+ x - left,
+ y - top,
+ width + left + right,
+ height + top + bottom,
+ };
+ }
+
+ bool Contains(VectorType point) const {
+ return point.x >= x &&
+ point.y >= y &&
+ point.x < x + width &&
+ point.y < y + height;
+ }
+
+ bool Intersects(const Rect& that) const {
+ bool xBetween = x > that.x0() && x < that.x1();
+ bool yBetween = y > that.y0() && y < that.y1();
+ return xBetween && yBetween;
+ }
+
+ // Write min()/max() tenary by hand so that we don't have to include <algorithm>
+ // This file is practically going to be included in every file in this project
+
+ static Rect Intersection(const Rect& a, const Rect& b) {
+ auto x0 = a.x0() > b.x0() ? a.x0() : b.x0(); // Max
+ auto y0 = a.y0() > b.y0() ? a.y0() : b.y0(); // Max
+ auto x1 = a.x1() < b.x1() ? a.x1() : b.x1(); // Min
+ auto y1 = a.y1() < b.y1() ? a.y1() : b.y1(); // Min
+ auto width = x1 - x0;
+ auto height = y1 - x0;
+ return Rect{ x0, y0, width, height };
+ }
+
+ static Rect Union(const Rect& a, const Rect& b) {
+ auto x0 = a.x0() < b.x0() ? a.x0() : b.x0(); // Min
+ auto y0 = a.y0() < b.y0() ? a.y0() : b.y0(); // Min
+ auto x1 = a.x1() > b.x1() ? a.x1() : b.x1(); // Max
+ auto y1 = a.y1() > b.y1() ? a.y1() : b.y1(); // Max
+ auto width = x1 - x0;
+ auto height = y1 - x0;
+ return Rect{ x0, y0, width, height };
+ }
+
+ friend bool operator==(const Rect<T>&, const Rect<T>&) = default;
+
+ Rect operator+(glm::vec<2, T> offset) const {
+ return { x + offset.x, y + offset.y, width, height };
+ }
+
+ Rect operator-(glm::vec<2, T> offset) const {
+ return { x - offset.x, y - offset.y, width, height };
+ }
+
+ Rect& operator+=(glm::vec<2, T> offset) {
+ x += offset.x;
+ y += offset.y;
+ return *this;
+ }
+
+ Rect& operator-=(glm::vec<2, T> offset) {
+ x -= offset.x;
+ y -= offset.y;
+ return *this;
+ }
+
+ template <class TTarget>
+ Rect<TTarget> Cast() const {
+ return {
+ static_cast<TTarget>(x),
+ static_cast<TTarget>(y),
+ static_cast<TTarget>(width),
+ static_cast<TTarget>(height),
+ };
+ }
+};
diff --git a/source/10-common/ScopeGuard.hpp b/source/10-common/ScopeGuard.hpp
new file mode 100644
index 0000000..28f3385
--- /dev/null
+++ b/source/10-common/ScopeGuard.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "Macros.hpp"
+
+#include <utility>
+
+template <class TCleanupFunc>
+class ScopeGuard {
+private:
+ TCleanupFunc mFunc;
+ bool mDismissed = false;
+
+public:
+ /// Specifically left this implicit so that constructs like
+ /// \code
+ /// ScopeGuard sg = [&]() { res.Cleanup(); };
+ /// \endcode
+ /// would work. It is highly discourage and unlikely that one would want to use ScopeGuard as a function
+ /// parameter, so the normal argument that implicit conversion are harmful doesn't really apply here.
+ // Deliberately not explicit to allow usages like: ScopeGuard var = lambda;
+ ScopeGuard(TCleanupFunc&& function) noexcept
+ : mFunc{ std::move(function) } {
+ }
+
+ ~ScopeGuard() noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ }
+
+ ScopeGuard(const ScopeGuard&) = delete;
+ ScopeGuard& operator=(const ScopeGuard&) = delete;
+
+ ScopeGuard(ScopeGuard&& that) noexcept
+ : mFunc{ std::move(that.mFunc) } {
+ that.Cancel();
+ }
+
+ ScopeGuard& operator=(ScopeGuard&& that) noexcept {
+ if (!mDismissed) {
+ mFunc();
+ }
+ this->mFunc = std::move(that.mFunc);
+ this->cancelled = std::exchange(that.cancelled, true);
+ }
+
+ void Dismiss() noexcept {
+ mDismissed = true;
+ }
+};
+
+template <class T>
+auto GuardDeletion(T* ptr) {
+ return ScopeGuard([ptr]() {
+ delete ptr;
+ });
+}
+
+#define SCOPE_GUARD(name) ScopeGuard name = [&]()
+#define DEFER ScopeGuard UNIQUE_NAME(scopeGuard) = [&]()
diff --git a/source/10-common/SmallVector.cpp b/source/10-common/SmallVector.cpp
new file mode 100644
index 0000000..c38e8a7
--- /dev/null
+++ b/source/10-common/SmallVector.cpp
@@ -0,0 +1,145 @@
+// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/SmallVector.cpp
+// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c
+// adapted in code style and utilities to fix this project
+
+//===- llvm/ADT/SmallVector.cpp - 'Normally small' vectors ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the SmallVector class.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SmallVector.hpp"
+
+#include <cstdlib>
+#include <stdexcept>
+#include <string>
+
+// Check that no bytes are wasted and everything is well-aligned.
+namespace {
+// These structures may cause binary compat warnings on AIX. Suppress the
+// warning since we are only using these types for the static assertions below.
+#if defined(_AIX)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Waix-compat"
+#endif
+struct Struct16B {
+ alignas(16) void* X;
+};
+struct Struct32B {
+ alignas(32) void* X;
+};
+#if defined(_AIX)
+# pragma GCC diagnostic pop
+#endif
+} // namespace
+static_assert(sizeof(SmallVector<void*, 0>) ==
+ sizeof(unsigned) * 2 + sizeof(void*),
+ "wasted space in SmallVector size 0");
+static_assert(alignof(SmallVector<Struct16B, 0>) >= alignof(Struct16B),
+ "wrong alignment for 16-byte aligned T");
+static_assert(alignof(SmallVector<Struct32B, 0>) >= alignof(Struct32B),
+ "wrong alignment for 32-byte aligned T");
+static_assert(sizeof(SmallVector<Struct16B, 0>) >= alignof(Struct16B),
+ "missing padding for 16-byte aligned T");
+static_assert(sizeof(SmallVector<Struct32B, 0>) >= alignof(Struct32B),
+ "missing padding for 32-byte aligned T");
+static_assert(sizeof(SmallVector<void*, 1>) ==
+ sizeof(unsigned) * 2 + sizeof(void*) * 2,
+ "wasted space in SmallVector size 1");
+
+static_assert(sizeof(SmallVector<char, 0>) ==
+ sizeof(void*) * 2 + sizeof(void*),
+ "1 byte elements have word-sized type for size and capacity");
+
+/// Report that MinSize doesn't fit into this vector's size type. Throws
+/// std::length_error or calls report_fatal_error.
+[[noreturn]] static void report_size_overflow(size_t MinSize, size_t MaxSize);
+static void report_size_overflow(size_t MinSize, size_t MaxSize) {
+ std::string Reason = "SmallVector unable to grow. Requested capacity (" +
+ std::to_string(MinSize) +
+ ") is larger than maximum value for size type (" +
+ std::to_string(MaxSize) + ")";
+ throw std::length_error(Reason);
+}
+
+/// Report that this vector is already at maximum capacity. Throws
+/// std::length_error or calls report_fatal_error.
+[[noreturn]] static void report_at_maximum_capacity(size_t MaxSize);
+static void report_at_maximum_capacity(size_t MaxSize) {
+ std::string Reason =
+ "SmallVector capacity unable to grow. Already at maximum size " +
+ std::to_string(MaxSize);
+ throw std::length_error(Reason);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+static size_t getNewCapacity(size_t MinSize, size_t TSize, size_t OldCapacity) {
+ constexpr size_t MaxSize = std::numeric_limits<Size_T>::max();
+
+ // Ensure we can fit the new capacity.
+ // This is only going to be applicable when the capacity is 32 bit.
+ if (MinSize > MaxSize)
+ report_size_overflow(MinSize, MaxSize);
+
+ // Ensure we can meet the guarantee of space for at least one more element.
+ // The above check alone will not catch the case where grow is called with a
+ // default MinSize of 0, but the current capacity cannot be increased.
+ // This is only going to be applicable when the capacity is 32 bit.
+ if (OldCapacity == MaxSize)
+ report_at_maximum_capacity(MaxSize);
+
+ // In theory 2*capacity can overflow if the capacity is 64 bit, but the
+ // original capacity would never be large enough for this to be a problem.
+ size_t NewCapacity = 2 * OldCapacity + 1; // Always grow.
+ return std::min(std::max(NewCapacity, MinSize), MaxSize);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+void* SmallVectorBase<Size_T>::mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity) {
+ NewCapacity = getNewCapacity<Size_T>(MinSize, TSize, this->capacity());
+ return malloc(NewCapacity * TSize);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+void SmallVectorBase<Size_T>::grow_pod(void* FirstEl, size_t MinSize, size_t TSize) {
+ size_t NewCapacity = getNewCapacity<Size_T>(MinSize, TSize, this->capacity());
+ void* NewElts;
+ if (BeginX == FirstEl) {
+ NewElts = malloc(NewCapacity * TSize);
+
+ // Copy the elements over. No need to run dtors on PODs.
+ memcpy(NewElts, this->BeginX, size() * TSize);
+ } else {
+ // If this wasn't grown from the inline copy, grow the allocated space.
+ NewElts = realloc(this->BeginX, NewCapacity * TSize);
+ }
+
+ this->BeginX = NewElts;
+ this->Capacity = NewCapacity;
+}
+
+template class SmallVectorBase<uint32_t>;
+
+// Disable the uint64_t instantiation for 32-bit builds.
+// Both uint32_t and uint64_t instantiations are needed for 64-bit builds.
+// This instantiation will never be used in 32-bit builds, and will cause
+// warnings when sizeof(Size_T) > sizeof(size_t).
+#if SIZE_MAX > UINT32_MAX
+template class SmallVectorBase<uint64_t>;
+
+// Assertions to ensure this #if stays in sync with SmallVectorSizeType.
+static_assert(sizeof(SmallVectorSizeType<char>) == sizeof(uint64_t),
+ "Expected SmallVectorBase<uint64_t> variant to be in use.");
+#else
+static_assert(sizeof(SmallVectorSizeType<char>) == sizeof(uint32_t),
+ "Expected SmallVectorBase<uint32_t> variant to be in use.");
+#endif
diff --git a/source/10-common/SmallVector.hpp b/source/10-common/SmallVector.hpp
new file mode 100644
index 0000000..e33a25d
--- /dev/null
+++ b/source/10-common/SmallVector.hpp
@@ -0,0 +1,1332 @@
+// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ADT/SmallVector.h
+// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c
+// adapted in code style and utilities to fix this project
+
+//===- llvm/ADT/SmallVector.h - 'Normally small' vectors --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file defines the SmallVector class.
+///
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4267) // The compiler detected a conversion from size_t to a smaller type.
+#endif
+
+#if __has_builtin(__builtin_expect) || defined(__GNUC__)
+# define LLVM_LIKELY(EXPR) __builtin_expect((bool)(EXPR), true)
+# define LLVM_UNLIKELY(EXPR) __builtin_expect((bool)(EXPR), false)
+#else
+# define LLVM_LIKELY(EXPR) (EXPR)
+# define LLVM_UNLIKELY(EXPR) (EXPR)
+#endif
+
+template <typename IteratorT>
+class iterator_range;
+
+/// This is all the stuff common to all SmallVectors.
+///
+/// The template parameter specifies the type which should be used to hold the
+/// Size and Capacity of the SmallVector, so it can be adjusted.
+/// Using 32 bit size is desirable to shrink the size of the SmallVector.
+/// Using 64 bit size is desirable for cases like SmallVector<char>, where a
+/// 32 bit size would limit the vector to ~4GB. SmallVectors are used for
+/// buffering bitcode output - which can exceed 4GB.
+template <class Size_T>
+class SmallVectorBase {
+protected:
+ void* BeginX;
+ Size_T Size = 0, Capacity;
+
+ /// The maximum value of the Size_T used.
+ static constexpr size_t SizeTypeMax() {
+ return std::numeric_limits<Size_T>::max();
+ }
+
+ SmallVectorBase() = delete;
+ SmallVectorBase(void* FirstEl, size_t TotalCapacity)
+ : BeginX(FirstEl), Capacity(TotalCapacity) {}
+
+ /// This is a helper for \a grow() that's out of line to reduce code
+ /// duplication. This function will report a fatal error if it can't grow at
+ /// least to \p MinSize.
+ void* mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity);
+
+ /// This is an implementation of the grow() method which only works
+ /// on POD-like data types and is out of line to reduce code duplication.
+ /// This function will report a fatal error if it cannot increase capacity.
+ void grow_pod(void* FirstEl, size_t MinSize, size_t TSize);
+
+public:
+ size_t size() const { return Size; }
+ size_t capacity() const { return Capacity; }
+
+ [[nodiscard]] bool empty() const { return !Size; }
+
+protected:
+ /// Set the array size to \p N, which the current array must have enough
+ /// capacity for.
+ ///
+ /// This does not construct or destroy any elements in the vector.
+ void set_size(size_t N) {
+ assert(N <= capacity());
+ Size = N;
+ }
+};
+
+template <class T>
+using SmallVectorSizeType =
+ typename std::conditional<sizeof(T) < 4 && sizeof(void*) >= 8, uint64_t, uint32_t>::type;
+
+/// Figure out the offset of the first element.
+template <class T, typename = void>
+struct SmallVectorAlignmentAndSize {
+ alignas(SmallVectorBase<SmallVectorSizeType<T>>) char Base[sizeof(
+ SmallVectorBase<SmallVectorSizeType<T>>)];
+ alignas(T) char FirstEl[sizeof(T)];
+};
+
+/// This is the part of SmallVectorTemplateBase which does not depend on whether
+/// the type T is a POD. The extra dummy template argument is used by ArrayRef
+/// to avoid unnecessarily requiring T to be complete.
+template <typename T, typename = void>
+class SmallVectorTemplateCommon
+ : public SmallVectorBase<SmallVectorSizeType<T>> {
+ using Base = SmallVectorBase<SmallVectorSizeType<T>>;
+
+ /// Find the address of the first element. For this pointer math to be valid
+ /// with small-size of 0 for T with lots of alignment, it's important that
+ /// SmallVectorStorage is properly-aligned even for small-size of 0.
+ void* getFirstEl() const {
+ return const_cast<void*>(reinterpret_cast<const void*>(
+ reinterpret_cast<const char*>(this) +
+ offsetof(SmallVectorAlignmentAndSize<T>, FirstEl)));
+ }
+ // Space after 'FirstEl' is clobbered, do not add any instance vars after it.
+
+protected:
+ SmallVectorTemplateCommon(size_t Size)
+ : Base(getFirstEl(), Size) {}
+
+ void grow_pod(size_t MinSize, size_t TSize) {
+ Base::grow_pod(getFirstEl(), MinSize, TSize);
+ }
+
+ /// Return true if this is a smallvector which has not had dynamic
+ /// memory allocated for it.
+ bool isSmall() const { return this->BeginX == getFirstEl(); }
+
+ /// Put this vector in a state of being small.
+ void resetToSmall() {
+ this->BeginX = getFirstEl();
+ this->Size = this->Capacity = 0; // FIXME: Setting Capacity to 0 is suspect.
+ }
+
+ /// Return true if V is an internal reference to the given range.
+ bool isReferenceToRange(const void* V, const void* First, const void* Last) const {
+ // Use std::less to avoid UB.
+ std::less<> LessThan;
+ return !LessThan(V, First) && LessThan(V, Last);
+ }
+
+ /// Return true if V is an internal reference to this vector.
+ bool isReferenceToStorage(const void* V) const {
+ return isReferenceToRange(V, this->begin(), this->end());
+ }
+
+ /// Return true if First and Last form a valid (possibly empty) range in this
+ /// vector's storage.
+ bool isRangeInStorage(const void* First, const void* Last) const {
+ // Use std::less to avoid UB.
+ std::less<> LessThan;
+ return !LessThan(First, this->begin()) && !LessThan(Last, First) &&
+ !LessThan(this->end(), Last);
+ }
+
+ /// Return true unless Elt will be invalidated by resizing the vector to
+ /// NewSize.
+ bool isSafeToReferenceAfterResize(const void* Elt, size_t NewSize) {
+ // Past the end.
+ if (LLVM_LIKELY(!isReferenceToStorage(Elt)))
+ return true;
+
+ // Return false if Elt will be destroyed by shrinking.
+ if (NewSize <= this->size())
+ return Elt < this->begin() + NewSize;
+
+ // Return false if we need to grow.
+ return NewSize <= this->capacity();
+ }
+
+ /// Check whether Elt will be invalidated by resizing the vector to NewSize.
+ void assertSafeToReferenceAfterResize(const void* Elt, size_t NewSize) {
+ assert(isSafeToReferenceAfterResize(Elt, NewSize) &&
+ "Attempting to reference an element of the vector in an operation "
+ "that invalidates it");
+ }
+
+ /// Check whether Elt will be invalidated by increasing the size of the
+ /// vector by N.
+ void assertSafeToAdd(const void* Elt, size_t N = 1) {
+ this->assertSafeToReferenceAfterResize(Elt, this->size() + N);
+ }
+
+ /// Check whether any part of the range will be invalidated by clearing.
+ void assertSafeToReferenceAfterClear(const T* From, const T* To) {
+ if (From == To)
+ return;
+ this->assertSafeToReferenceAfterResize(From, 0);
+ this->assertSafeToReferenceAfterResize(To - 1, 0);
+ }
+ template <
+ class ItTy,
+ std::enable_if_t<!std::is_same<std::remove_const_t<ItTy>, T*>::value,
+ bool> = false>
+ void assertSafeToReferenceAfterClear(ItTy, ItTy) {}
+
+ /// Check whether any part of the range will be invalidated by growing.
+ void assertSafeToAddRange(const T* From, const T* To) {
+ if (From == To)
+ return;
+ this->assertSafeToAdd(From, To - From);
+ this->assertSafeToAdd(To - 1, To - From);
+ }
+ template <
+ class ItTy,
+ std::enable_if_t<!std::is_same<std::remove_const_t<ItTy>, T*>::value,
+ bool> = false>
+ void assertSafeToAddRange(ItTy, ItTy) {}
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ template <class U>
+ static const T* reserveForParamAndGetAddressImpl(U* This, const T& Elt, size_t N) {
+ size_t NewSize = This->size() + N;
+ if (LLVM_LIKELY(NewSize <= This->capacity()))
+ return &Elt;
+
+ bool ReferencesStorage = false;
+ int64_t Index = -1;
+ if (!U::TakesParamByValue) {
+ if (LLVM_UNLIKELY(This->isReferenceToStorage(&Elt))) {
+ ReferencesStorage = true;
+ Index = &Elt - This->begin();
+ }
+ }
+ This->grow(NewSize);
+ return ReferencesStorage ? This->begin() + Index : &Elt;
+ }
+
+public:
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using value_type = T;
+ using iterator = T*;
+ using const_iterator = const T*;
+
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+
+ using Base::capacity;
+ using Base::empty;
+ using Base::size;
+
+ // forward iterator creation methods.
+ iterator begin() { return (iterator)this->BeginX; }
+ const_iterator begin() const { return (const_iterator)this->BeginX; }
+ iterator end() { return begin() + size(); }
+ const_iterator end() const { return begin() + size(); }
+
+ // reverse iterator creation methods.
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
+
+ size_type size_in_bytes() const { return size() * sizeof(T); }
+ size_type max_size() const {
+ return std::min(this->SizeTypeMax(), size_type(-1) / sizeof(T));
+ }
+
+ size_t capacity_in_bytes() const { return capacity() * sizeof(T); }
+
+ /// Return a pointer to the vector's buffer, even if empty().
+ pointer data() { return pointer(begin()); }
+ /// Return a pointer to the vector's buffer, even if empty().
+ const_pointer data() const { return const_pointer(begin()); }
+
+ reference operator[](size_type idx) {
+ assert(idx < size());
+ return begin()[idx];
+ }
+ const_reference operator[](size_type idx) const {
+ assert(idx < size());
+ return begin()[idx];
+ }
+
+ reference front() {
+ assert(!empty());
+ return begin()[0];
+ }
+ const_reference front() const {
+ assert(!empty());
+ return begin()[0];
+ }
+
+ reference back() {
+ assert(!empty());
+ return end()[-1];
+ }
+ const_reference back() const {
+ assert(!empty());
+ return end()[-1];
+ }
+};
+
+/// SmallVectorTemplateBase<TriviallyCopyable = false> - This is where we put
+/// method implementations that are designed to work with non-trivial T's.
+///
+/// We approximate is_trivially_copyable with trivial move/copy construction and
+/// trivial destruction. While the standard doesn't specify that you're allowed
+/// copy these types with memcpy, there is no way for the type to observe this.
+/// This catches the important case of std::pair<POD, POD>, which is not
+/// trivially assignable.
+template <typename T, bool = (std::is_trivially_copy_constructible<T>::value) && (std::is_trivially_move_constructible<T>::value) && std::is_trivially_destructible<T>::value>
+class SmallVectorTemplateBase : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
+protected:
+ static constexpr bool TakesParamByValue = false;
+ using ValueParamT = const T&;
+
+ SmallVectorTemplateBase(size_t Size)
+ : SmallVectorTemplateCommon<T>(Size) {}
+
+ static void destroy_range(T* S, T* E) {
+ while (S != E) {
+ --E;
+ E->~T();
+ }
+ }
+
+ /// Move the range [I, E) into the uninitialized memory starting with "Dest",
+ /// constructing elements as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_move(It1 I, It1 E, It2 Dest) {
+ std::uninitialized_copy(std::make_move_iterator(I),
+ std::make_move_iterator(E),
+ Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory starting with "Dest",
+ /// constructing elements as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_copy(It1 I, It1 E, It2 Dest) {
+ std::uninitialized_copy(I, E, Dest);
+ }
+
+ /// Grow the allocated memory (without initializing new elements), doubling
+ /// the size of the allocated memory. Guarantees space for at least one more
+ /// element, or MinSize more elements if specified.
+ void grow(size_t MinSize = 0);
+
+ /// Create a new allocation big enough for \p MinSize and pass back its size
+ /// in \p NewCapacity. This is the first section of \a grow().
+ T* mallocForGrow(size_t MinSize, size_t& NewCapacity) {
+ return static_cast<T*>(
+ SmallVectorBase<SmallVectorSizeType<T>>::mallocForGrow(
+ MinSize, sizeof(T), NewCapacity));
+ }
+
+ /// Move existing elements over to the new allocation \p NewElts, the middle
+ /// section of \a grow().
+ void moveElementsForGrow(T* NewElts);
+
+ /// Transfer ownership of the allocation, finishing up \a grow().
+ void takeAllocationForGrow(T* NewElts, size_t NewCapacity);
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) {
+ return const_cast<T*>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ static T&& forward_value_param(T&& V) { return std::move(V); }
+ static const T& forward_value_param(const T& V) { return V; }
+
+ void growAndAssign(size_t NumElts, const T& Elt) {
+ // Grow manually in case Elt is an internal reference.
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(NumElts, NewCapacity);
+ std::uninitialized_fill_n(NewElts, NumElts, Elt);
+ this->destroy_range(this->begin(), this->end());
+ takeAllocationForGrow(NewElts, NewCapacity);
+ this->set_size(NumElts);
+ }
+
+ template <typename... ArgTypes>
+ T& growAndEmplaceBack(ArgTypes&&... Args) {
+ // Grow manually in case one of Args is an internal reference.
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(0, NewCapacity);
+ ::new ((void*)(NewElts + this->size())) T(std::forward<ArgTypes>(Args)...);
+ moveElementsForGrow(NewElts);
+ takeAllocationForGrow(NewElts, NewCapacity);
+ this->set_size(this->size() + 1);
+ return this->back();
+ }
+
+public:
+ void push_back(const T& Elt) {
+ const T* EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void*)this->end()) T(*EltPtr);
+ this->set_size(this->size() + 1);
+ }
+
+ void push_back(T&& Elt) {
+ T* EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void*)this->end()) T(::std::move(*EltPtr));
+ this->set_size(this->size() + 1);
+ }
+
+ void pop_back() {
+ this->set_size(this->size() - 1);
+ this->end()->~T();
+ }
+};
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::grow(size_t MinSize) {
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(MinSize, NewCapacity);
+ moveElementsForGrow(NewElts);
+ takeAllocationForGrow(NewElts, NewCapacity);
+}
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::moveElementsForGrow(
+ T* NewElts) {
+ // Move the elements over.
+ this->uninitialized_move(this->begin(), this->end(), NewElts);
+
+ // Destroy the original elements.
+ destroy_range(this->begin(), this->end());
+}
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::takeAllocationForGrow(
+ T* NewElts, size_t NewCapacity) {
+ // If this wasn't grown from the inline copy, deallocate the old space.
+ if (!this->isSmall())
+ free(this->begin());
+
+ this->BeginX = NewElts;
+ this->Capacity = NewCapacity;
+}
+
+/// SmallVectorTemplateBase<TriviallyCopyable = true> - This is where we put
+/// method implementations that are designed to work with trivially copyable
+/// T's. This allows using memcpy in place of copy/move construction and
+/// skipping destruction.
+template <typename T>
+class SmallVectorTemplateBase<T, true> : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
+protected:
+ /// True if it's cheap enough to take parameters by value. Doing so avoids
+ /// overhead related to mitigations for reference invalidation.
+ static constexpr bool TakesParamByValue = sizeof(T) <= 2 * sizeof(void*);
+
+ /// Either const T& or T, depending on whether it's cheap enough to take
+ /// parameters by value.
+ using ValueParamT =
+ typename std::conditional<TakesParamByValue, T, const T&>::type;
+
+ SmallVectorTemplateBase(size_t Size)
+ : SmallVectorTemplateCommon<T>(Size) {}
+
+ // No need to do a destroy loop for POD's.
+ static void destroy_range(T*, T*) {}
+
+ /// Move the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_move(It1 I, It1 E, It2 Dest) {
+ // Just do a copy.
+ uninitialized_copy(I, E, Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_copy(It1 I, It1 E, It2 Dest) {
+ // Arbitrary iterator types; just use the basic implementation.
+ std::uninitialized_copy(I, E, Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename T1, typename T2>
+ static void uninitialized_copy(
+ T1* I, T1* E, T2* Dest, std::enable_if_t<std::is_same<typename std::remove_const<T1>::type, T2>::value>* = nullptr) {
+ // Use memcpy for PODs iterated by pointers (which includes SmallVector
+ // iterators): std::uninitialized_copy optimizes to memmove, but we can
+ // use memcpy here. Note that I and E are iterators and thus might be
+ // invalid for memcpy if they are equal.
+ if (I != E)
+ memcpy(reinterpret_cast<void*>(Dest), I, (E - I) * sizeof(T));
+ }
+
+ /// Double the size of the allocated memory, guaranteeing space for at
+ /// least one more element or MinSize if specified.
+ void grow(size_t MinSize = 0) { this->grow_pod(MinSize, sizeof(T)); }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) {
+ return const_cast<T*>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ /// Copy \p V or return a reference, depending on \a ValueParamT.
+ static ValueParamT forward_value_param(ValueParamT V) { return V; }
+
+ void growAndAssign(size_t NumElts, T Elt) {
+ // Elt has been copied in case it's an internal reference, side-stepping
+ // reference invalidation problems without losing the realloc optimization.
+ this->set_size(0);
+ this->grow(NumElts);
+ std::uninitialized_fill_n(this->begin(), NumElts, Elt);
+ this->set_size(NumElts);
+ }
+
+ template <typename... ArgTypes>
+ T& growAndEmplaceBack(ArgTypes&&... Args) {
+ // Use push_back with a copy in case Args has an internal reference,
+ // side-stepping reference invalidation problems without losing the realloc
+ // optimization.
+ push_back(T(std::forward<ArgTypes>(Args)...));
+ return this->back();
+ }
+
+public:
+ void push_back(ValueParamT Elt) {
+ const T* EltPtr = reserveForParamAndGetAddress(Elt);
+ memcpy(reinterpret_cast<void*>(this->end()), EltPtr, sizeof(T));
+ this->set_size(this->size() + 1);
+ }
+
+ void pop_back() { this->set_size(this->size() - 1); }
+};
+
+/// This class consists of common code factored out of the SmallVector class to
+/// reduce code duplication based on the SmallVector 'N' template parameter.
+template <typename T>
+class SmallVectorImpl : public SmallVectorTemplateBase<T> {
+ using SuperClass = SmallVectorTemplateBase<T>;
+
+public:
+ using iterator = typename SuperClass::iterator;
+ using const_iterator = typename SuperClass::const_iterator;
+ using reference = typename SuperClass::reference;
+ using size_type = typename SuperClass::size_type;
+
+protected:
+ using SmallVectorTemplateBase<T>::TakesParamByValue;
+ using ValueParamT = typename SuperClass::ValueParamT;
+
+ // Default ctor - Initialize to empty.
+ explicit SmallVectorImpl(unsigned N)
+ : SmallVectorTemplateBase<T>(N) {}
+
+ void assignRemote(SmallVectorImpl&& RHS) {
+ this->destroy_range(this->begin(), this->end());
+ if (!this->isSmall())
+ free(this->begin());
+ this->BeginX = RHS.BeginX;
+ this->Size = RHS.Size;
+ this->Capacity = RHS.Capacity;
+ RHS.resetToSmall();
+ }
+
+public:
+ SmallVectorImpl(const SmallVectorImpl&) = delete;
+
+ ~SmallVectorImpl() {
+ // Subclass has already destructed this vector's elements.
+ // If this wasn't grown from the inline copy, deallocate the old space.
+ if (!this->isSmall())
+ free(this->begin());
+ }
+
+ void clear() {
+ this->destroy_range(this->begin(), this->end());
+ this->Size = 0;
+ }
+
+private:
+ // Make set_size() private to avoid misuse in subclasses.
+ using SuperClass::set_size;
+
+ template <bool ForOverwrite>
+ void resizeImpl(size_type N) {
+ if (N == this->size())
+ return;
+
+ if (N < this->size()) {
+ this->truncate(N);
+ return;
+ }
+
+ this->reserve(N);
+ for (auto I = this->end(), E = this->begin() + N; I != E; ++I)
+ if (ForOverwrite)
+ new (&*I) T;
+ else
+ new (&*I) T();
+ this->set_size(N);
+ }
+
+public:
+ void resize(size_type N) { resizeImpl<false>(N); }
+
+ /// Like resize, but \ref T is POD, the new values won't be initialized.
+ void resize_for_overwrite(size_type N) { resizeImpl<true>(N); }
+
+ /// Like resize, but requires that \p N is less than \a size().
+ void truncate(size_type N) {
+ assert(this->size() >= N && "Cannot increase size with truncate");
+ this->destroy_range(this->begin() + N, this->end());
+ this->set_size(N);
+ }
+
+ void resize(size_type N, ValueParamT NV) {
+ if (N == this->size())
+ return;
+
+ if (N < this->size()) {
+ this->truncate(N);
+ return;
+ }
+
+ // N > this->size(). Defer to append.
+ this->append(N - this->size(), NV);
+ }
+
+ void reserve(size_type N) {
+ if (this->capacity() < N)
+ this->grow(N);
+ }
+
+ void pop_back_n(size_type NumItems) {
+ assert(this->size() >= NumItems);
+ truncate(this->size() - NumItems);
+ }
+
+ [[nodiscard]] T pop_back_val() {
+ T Result = ::std::move(this->back());
+ this->pop_back();
+ return Result;
+ }
+
+ void swap(SmallVectorImpl& RHS);
+
+ /// Add the specified range to the end of the SmallVector.
+ template <typename in_iter,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<in_iter>::iterator_category,
+ std::input_iterator_tag>::value>>
+ void append(in_iter in_start, in_iter in_end) {
+ this->assertSafeToAddRange(in_start, in_end);
+ size_type NumInputs = std::distance(in_start, in_end);
+ this->reserve(this->size() + NumInputs);
+ this->uninitialized_copy(in_start, in_end, this->end());
+ this->set_size(this->size() + NumInputs);
+ }
+
+ /// Append \p NumInputs copies of \p Elt to the end.
+ void append(size_type NumInputs, ValueParamT Elt) {
+ const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumInputs);
+ std::uninitialized_fill_n(this->end(), NumInputs, *EltPtr);
+ this->set_size(this->size() + NumInputs);
+ }
+
+ void append(std::initializer_list<T> IL) {
+ append(IL.begin(), IL.end());
+ }
+
+ void append(const SmallVectorImpl& RHS) { append(RHS.begin(), RHS.end()); }
+
+ void assign(size_type NumElts, ValueParamT Elt) {
+ // Note that Elt could be an internal reference.
+ if (NumElts > this->capacity()) {
+ this->growAndAssign(NumElts, Elt);
+ return;
+ }
+
+ // Assign over existing elements.
+ std::fill_n(this->begin(), std::min(NumElts, this->size()), Elt);
+ if (NumElts > this->size())
+ std::uninitialized_fill_n(this->end(), NumElts - this->size(), Elt);
+ else if (NumElts < this->size())
+ this->destroy_range(this->begin() + NumElts, this->end());
+ this->set_size(NumElts);
+ }
+
+ // FIXME: Consider assigning over existing elements, rather than clearing &
+ // re-initializing them - for all assign(...) variants.
+
+ template <typename in_iter,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<in_iter>::iterator_category,
+ std::input_iterator_tag>::value>>
+ void assign(in_iter in_start, in_iter in_end) {
+ this->assertSafeToReferenceAfterClear(in_start, in_end);
+ clear();
+ append(in_start, in_end);
+ }
+
+ void assign(std::initializer_list<T> IL) {
+ clear();
+ append(IL);
+ }
+
+ void assign(const SmallVectorImpl& RHS) { assign(RHS.begin(), RHS.end()); }
+
+ iterator erase(const_iterator CI) {
+ // Just cast away constness because this is a non-const member function.
+ iterator I = const_cast<iterator>(CI);
+
+ assert(this->isReferenceToStorage(CI) && "Iterator to erase is out of bounds.");
+
+ iterator N = I;
+ // Shift all elts down one.
+ std::move(I + 1, this->end(), I);
+ // Drop the last elt.
+ this->pop_back();
+ return (N);
+ }
+
+ iterator erase(const_iterator CS, const_iterator CE) {
+ // Just cast away constness because this is a non-const member function.
+ iterator S = const_cast<iterator>(CS);
+ iterator E = const_cast<iterator>(CE);
+
+ assert(this->isRangeInStorage(S, E) && "Range to erase is out of bounds.");
+
+ iterator N = S;
+ // Shift all elts down.
+ iterator I = std::move(E, this->end(), S);
+ // Drop the last elts.
+ this->destroy_range(I, this->end());
+ this->set_size(I - this->begin());
+ return (N);
+ }
+
+private:
+ template <class ArgType>
+ iterator insert_one_impl(iterator I, ArgType&& Elt) {
+ // Callers ensure that ArgType is derived from T.
+ static_assert(
+ std::is_same<std::remove_const_t<std::remove_reference_t<ArgType>>,
+ T>::value,
+ "ArgType must be derived from T!");
+
+ if (I == this->end()) { // Important special case for empty vector.
+ this->push_back(::std::forward<ArgType>(Elt));
+ return this->end() - 1;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Grow if necessary.
+ size_t Index = I - this->begin();
+ std::remove_reference_t<ArgType>* EltPtr =
+ this->reserveForParamAndGetAddress(Elt);
+ I = this->begin() + Index;
+
+ ::new ((void*)this->end()) T(::std::move(this->back()));
+ // Push everything else over.
+ std::move_backward(I, this->end() - 1, this->end());
+ this->set_size(this->size() + 1);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ static_assert(!TakesParamByValue || std::is_same<ArgType, T>::value,
+ "ArgType must be 'T' when taking by value!");
+ if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end()))
+ ++EltPtr;
+
+ *I = ::std::forward<ArgType>(*EltPtr);
+ return I;
+ }
+
+public:
+ iterator insert(iterator I, T&& Elt) {
+ return insert_one_impl(I, this->forward_value_param(std::move(Elt)));
+ }
+
+ iterator insert(iterator I, const T& Elt) {
+ return insert_one_impl(I, this->forward_value_param(Elt));
+ }
+
+ iterator insert(iterator I, size_type NumToInsert, ValueParamT Elt) {
+ // Convert iterator to elt# to avoid invalidating iterator when we reserve()
+ size_t InsertElt = I - this->begin();
+
+ if (I == this->end()) { // Important special case for empty vector.
+ append(NumToInsert, Elt);
+ return this->begin() + InsertElt;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Ensure there is enough space, and get the (maybe updated) address of
+ // Elt.
+ const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumToInsert);
+
+ // Uninvalidate the iterator.
+ I = this->begin() + InsertElt;
+
+ // If there are more elements between the insertion point and the end of the
+ // range than there are being inserted, we can use a simple approach to
+ // insertion. Since we already reserved space, we know that this won't
+ // reallocate the vector.
+ if (size_t(this->end() - I) >= NumToInsert) {
+ T* OldEnd = this->end();
+ append(std::move_iterator<iterator>(this->end() - NumToInsert),
+ std::move_iterator<iterator>(this->end()));
+
+ // Copy the existing elements that get replaced.
+ std::move_backward(I, OldEnd - NumToInsert, OldEnd);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
+ std::fill_n(I, NumToInsert, *EltPtr);
+ return I;
+ }
+
+ // Otherwise, we're inserting more elements than exist already, and we're
+ // not inserting at the end.
+
+ // Move over the elements that we're about to overwrite.
+ T* OldEnd = this->end();
+ this->set_size(this->size() + NumToInsert);
+ size_t NumOverwritten = OldEnd - I;
+ this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
+ // Replace the overwritten part.
+ std::fill_n(I, NumOverwritten, *EltPtr);
+
+ // Insert the non-overwritten middle part.
+ std::uninitialized_fill_n(OldEnd, NumToInsert - NumOverwritten, *EltPtr);
+ return I;
+ }
+
+ template <typename ItTy,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<ItTy>::iterator_category,
+ std::input_iterator_tag>::value>>
+ iterator insert(iterator I, ItTy From, ItTy To) {
+ // Convert iterator to elt# to avoid invalidating iterator when we reserve()
+ size_t InsertElt = I - this->begin();
+
+ if (I == this->end()) { // Important special case for empty vector.
+ append(From, To);
+ return this->begin() + InsertElt;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Check that the reserve that follows doesn't invalidate the iterators.
+ this->assertSafeToAddRange(From, To);
+
+ size_t NumToInsert = std::distance(From, To);
+
+ // Ensure there is enough space.
+ reserve(this->size() + NumToInsert);
+
+ // Uninvalidate the iterator.
+ I = this->begin() + InsertElt;
+
+ // If there are more elements between the insertion point and the end of the
+ // range than there are being inserted, we can use a simple approach to
+ // insertion. Since we already reserved space, we know that this won't
+ // reallocate the vector.
+ if (size_t(this->end() - I) >= NumToInsert) {
+ T* OldEnd = this->end();
+ append(std::move_iterator<iterator>(this->end() - NumToInsert),
+ std::move_iterator<iterator>(this->end()));
+
+ // Copy the existing elements that get replaced.
+ std::move_backward(I, OldEnd - NumToInsert, OldEnd);
+
+ std::copy(From, To, I);
+ return I;
+ }
+
+ // Otherwise, we're inserting more elements than exist already, and we're
+ // not inserting at the end.
+
+ // Move over the elements that we're about to overwrite.
+ T* OldEnd = this->end();
+ this->set_size(this->size() + NumToInsert);
+ size_t NumOverwritten = OldEnd - I;
+ this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten);
+
+ // Replace the overwritten part.
+ for (T* J = I; NumOverwritten > 0; --NumOverwritten) {
+ *J = *From;
+ ++J;
+ ++From;
+ }
+
+ // Insert the non-overwritten middle part.
+ this->uninitialized_copy(From, To, OldEnd);
+ return I;
+ }
+
+ void insert(iterator I, std::initializer_list<T> IL) {
+ insert(I, IL.begin(), IL.end());
+ }
+
+ template <typename... ArgTypes>
+ reference emplace_back(ArgTypes&&... Args) {
+ if (LLVM_UNLIKELY(this->size() >= this->capacity()))
+ return this->growAndEmplaceBack(std::forward<ArgTypes>(Args)...);
+
+ ::new ((void*)this->end()) T(std::forward<ArgTypes>(Args)...);
+ this->set_size(this->size() + 1);
+ return this->back();
+ }
+
+ SmallVectorImpl& operator=(const SmallVectorImpl& RHS);
+
+ SmallVectorImpl& operator=(SmallVectorImpl&& RHS);
+
+ bool operator==(const SmallVectorImpl& RHS) const {
+ if (this->size() != RHS.size()) return false;
+ return std::equal(this->begin(), this->end(), RHS.begin());
+ }
+ bool operator!=(const SmallVectorImpl& RHS) const {
+ return !(*this == RHS);
+ }
+
+ bool operator<(const SmallVectorImpl& RHS) const {
+ return std::lexicographical_compare(this->begin(), this->end(), RHS.begin(), RHS.end());
+ }
+};
+
+template <typename T>
+void SmallVectorImpl<T>::swap(SmallVectorImpl<T>& RHS) {
+ if (this == &RHS) return;
+
+ // We can only avoid copying elements if neither vector is small.
+ if (!this->isSmall() && !RHS.isSmall()) {
+ std::swap(this->BeginX, RHS.BeginX);
+ std::swap(this->Size, RHS.Size);
+ std::swap(this->Capacity, RHS.Capacity);
+ return;
+ }
+ this->reserve(RHS.size());
+ RHS.reserve(this->size());
+
+ // Swap the shared elements.
+ size_t NumShared = this->size();
+ if (NumShared > RHS.size()) NumShared = RHS.size();
+ for (size_type i = 0; i != NumShared; ++i)
+ std::swap((*this)[i], RHS[i]);
+
+ // Copy over the extra elts.
+ if (this->size() > RHS.size()) {
+ size_t EltDiff = this->size() - RHS.size();
+ this->uninitialized_copy(this->begin() + NumShared, this->end(), RHS.end());
+ RHS.set_size(RHS.size() + EltDiff);
+ this->destroy_range(this->begin() + NumShared, this->end());
+ this->set_size(NumShared);
+ } else if (RHS.size() > this->size()) {
+ size_t EltDiff = RHS.size() - this->size();
+ this->uninitialized_copy(RHS.begin() + NumShared, RHS.end(), this->end());
+ this->set_size(this->size() + EltDiff);
+ this->destroy_range(RHS.begin() + NumShared, RHS.end());
+ RHS.set_size(NumShared);
+ }
+}
+
+template <typename T>
+SmallVectorImpl<T>& SmallVectorImpl<T>::
+operator=(const SmallVectorImpl<T>& RHS) {
+ // Avoid self-assignment.
+ if (this == &RHS) return *this;
+
+ // If we already have sufficient space, assign the common elements, then
+ // destroy any excess.
+ size_t RHSSize = RHS.size();
+ size_t CurSize = this->size();
+ if (CurSize >= RHSSize) {
+ // Assign common elements.
+ iterator NewEnd;
+ if (RHSSize)
+ NewEnd = std::copy(RHS.begin(), RHS.begin() + RHSSize, this->begin());
+ else
+ NewEnd = this->begin();
+
+ // Destroy excess elements.
+ this->destroy_range(NewEnd, this->end());
+
+ // Trim.
+ this->set_size(RHSSize);
+ return *this;
+ }
+
+ // If we have to grow to have enough elements, destroy the current elements.
+ // This allows us to avoid copying them during the grow.
+ // FIXME: don't do this if they're efficiently moveable.
+ if (this->capacity() < RHSSize) {
+ // Destroy current elements.
+ this->clear();
+ CurSize = 0;
+ this->grow(RHSSize);
+ } else if (CurSize) {
+ // Otherwise, use assignment for the already-constructed elements.
+ std::copy(RHS.begin(), RHS.begin() + CurSize, this->begin());
+ }
+
+ // Copy construct the new elements in place.
+ this->uninitialized_copy(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize);
+
+ // Set end.
+ this->set_size(RHSSize);
+ return *this;
+}
+
+template <typename T>
+SmallVectorImpl<T>& SmallVectorImpl<T>::operator=(SmallVectorImpl<T>&& RHS) {
+ // Avoid self-assignment.
+ if (this == &RHS) return *this;
+
+ // If the RHS isn't small, clear this vector and then steal its buffer.
+ if (!RHS.isSmall()) {
+ this->assignRemote(std::move(RHS));
+ return *this;
+ }
+
+ // If we already have sufficient space, assign the common elements, then
+ // destroy any excess.
+ size_t RHSSize = RHS.size();
+ size_t CurSize = this->size();
+ if (CurSize >= RHSSize) {
+ // Assign common elements.
+ iterator NewEnd = this->begin();
+ if (RHSSize)
+ NewEnd = std::move(RHS.begin(), RHS.end(), NewEnd);
+
+ // Destroy excess elements and trim the bounds.
+ this->destroy_range(NewEnd, this->end());
+ this->set_size(RHSSize);
+
+ // Clear the RHS.
+ RHS.clear();
+
+ return *this;
+ }
+
+ // If we have to grow to have enough elements, destroy the current elements.
+ // This allows us to avoid copying them during the grow.
+ // FIXME: this may not actually make any sense if we can efficiently move
+ // elements.
+ if (this->capacity() < RHSSize) {
+ // Destroy current elements.
+ this->clear();
+ CurSize = 0;
+ this->grow(RHSSize);
+ } else if (CurSize) {
+ // Otherwise, use assignment for the already-constructed elements.
+ std::move(RHS.begin(), RHS.begin() + CurSize, this->begin());
+ }
+
+ // Move-construct the new elements in place.
+ this->uninitialized_move(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize);
+
+ // Set end.
+ this->set_size(RHSSize);
+
+ RHS.clear();
+ return *this;
+}
+
+/// Storage for the SmallVector elements. This is specialized for the N=0 case
+/// to avoid allocating unnecessary storage.
+template <typename T, unsigned N>
+struct SmallVectorStorage {
+ alignas(T) char InlineElts[N * sizeof(T)];
+};
+
+/// We need the storage to be properly aligned even for small-size of 0 so that
+/// the pointer math in \a SmallVectorTemplateCommon::getFirstEl() is
+/// well-defined.
+template <typename T>
+struct alignas(T) SmallVectorStorage<T, 0> {};
+
+/// Forward declaration of SmallVector so that
+/// calculateSmallVectorDefaultInlinedElements can reference
+/// `sizeof(SmallVector<T, 0>)`.
+template <typename T, unsigned N>
+class SmallVector;
+
+/// Helper class for calculating the default number of inline elements for
+/// `SmallVector<T>`.
+///
+/// This should be migrated to a constexpr function when our minimum
+/// compiler support is enough for multi-statement constexpr functions.
+template <typename T>
+struct CalculateSmallVectorDefaultInlinedElements {
+ // Parameter controlling the default number of inlined elements
+ // for `SmallVector<T>`.
+ //
+ // The default number of inlined elements ensures that
+ // 1. There is at least one inlined element.
+ // 2. `sizeof(SmallVector<T>) <= kPreferredSmallVectorSizeof` unless
+ // it contradicts 1.
+ static constexpr size_t kPreferredSmallVectorSizeof = 64;
+
+ // static_assert that sizeof(T) is not "too big".
+ //
+ // Because our policy guarantees at least one inlined element, it is possible
+ // for an arbitrarily large inlined element to allocate an arbitrarily large
+ // amount of inline storage. We generally consider it an antipattern for a
+ // SmallVector to allocate an excessive amount of inline storage, so we want
+ // to call attention to these cases and make sure that users are making an
+ // intentional decision if they request a lot of inline storage.
+ //
+ // We want this assertion to trigger in pathological cases, but otherwise
+ // not be too easy to hit. To accomplish that, the cutoff is actually somewhat
+ // larger than kPreferredSmallVectorSizeof (otherwise,
+ // `SmallVector<SmallVector<T>>` would be one easy way to trip it, and that
+ // pattern seems useful in practice).
+ //
+ // One wrinkle is that this assertion is in theory non-portable, since
+ // sizeof(T) is in general platform-dependent. However, we don't expect this
+ // to be much of an issue, because most LLVM development happens on 64-bit
+ // hosts, and therefore sizeof(T) is expected to *decrease* when compiled for
+ // 32-bit hosts, dodging the issue. The reverse situation, where development
+ // happens on a 32-bit host and then fails due to sizeof(T) *increasing* on a
+ // 64-bit host, is expected to be very rare.
+ static_assert(
+ sizeof(T) <= 256,
+ "You are trying to use a default number of inlined elements for "
+ "`SmallVector<T>` but `sizeof(T)` is really big! Please use an "
+ "explicit number of inlined elements with `SmallVector<T, N>` to make "
+ "sure you really want that much inline storage.");
+
+ // Discount the size of the header itself when calculating the maximum inline
+ // bytes.
+ static constexpr size_t PreferredInlineBytes =
+ kPreferredSmallVectorSizeof - sizeof(SmallVector<T, 0>);
+ static constexpr size_t NumElementsThatFit = PreferredInlineBytes / sizeof(T);
+ static constexpr size_t value =
+ NumElementsThatFit == 0 ? 1 : NumElementsThatFit;
+};
+
+/// This is a 'vector' (really, a variable-sized array), optimized
+/// for the case when the array is small. It contains some number of elements
+/// in-place, which allows it to avoid heap allocation when the actual number of
+/// elements is below that threshold. This allows normal "small" cases to be
+/// fast without losing generality for large inputs.
+///
+/// \note
+/// In the absence of a well-motivated choice for the number of inlined
+/// elements \p N, it is recommended to use \c SmallVector<T> (that is,
+/// omitting the \p N). This will choose a default number of inlined elements
+/// reasonable for allocation on the stack (for example, trying to keep \c
+/// sizeof(SmallVector<T>) around 64 bytes).
+///
+/// \warning This does not attempt to be exception safe.
+///
+/// \see https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h
+template <typename T,
+ unsigned N = CalculateSmallVectorDefaultInlinedElements<T>::value>
+class SmallVector : public SmallVectorImpl<T>,
+ SmallVectorStorage<T, N> {
+public:
+ SmallVector()
+ : SmallVectorImpl<T>(N) {}
+
+ ~SmallVector() {
+ // Destroy the constructed elements in the vector.
+ this->destroy_range(this->begin(), this->end());
+ }
+
+ explicit SmallVector(size_t Size, const T& Value = T())
+ : SmallVectorImpl<T>(N) {
+ this->assign(Size, Value);
+ }
+
+ template <typename ItTy,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<ItTy>::iterator_category,
+ std::input_iterator_tag>::value>>
+ SmallVector(ItTy S, ItTy E)
+ : SmallVectorImpl<T>(N) {
+ this->append(S, E);
+ }
+
+ template <typename RangeTy>
+ explicit SmallVector(const iterator_range<RangeTy>& R)
+ : SmallVectorImpl<T>(N) {
+ this->append(R.begin(), R.end());
+ }
+
+ SmallVector(std::initializer_list<T> IL)
+ : SmallVectorImpl<T>(N) {
+ this->assign(IL);
+ }
+
+ SmallVector(const SmallVector& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(RHS);
+ }
+
+ SmallVector& operator=(const SmallVector& RHS) {
+ SmallVectorImpl<T>::operator=(RHS);
+ return *this;
+ }
+
+ SmallVector(SmallVector&& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ }
+
+ SmallVector(SmallVectorImpl<T>&& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ }
+
+ SmallVector& operator=(SmallVector&& RHS) {
+ if (N) {
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ return *this;
+ }
+ // SmallVectorImpl<T>::operator= does not leverage N==0. Optimize the
+ // case.
+ if (this == &RHS)
+ return *this;
+ if (RHS.empty()) {
+ this->destroy_range(this->begin(), this->end());
+ this->Size = 0;
+ } else {
+ this->assignRemote(std::move(RHS));
+ }
+ return *this;
+ }
+
+ SmallVector& operator=(SmallVectorImpl<T>&& RHS) {
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ return *this;
+ }
+
+ SmallVector& operator=(std::initializer_list<T> IL) {
+ this->assign(IL);
+ return *this;
+ }
+};
+
+template <typename T, unsigned N>
+inline size_t capacity_in_bytes(const SmallVector<T, N>& X) {
+ return X.capacity_in_bytes();
+}
+
+template <typename RangeType>
+using ValueTypeFromRangeType =
+ typename std::remove_const<typename std::remove_reference<
+ decltype(*std::begin(std::declval<RangeType&>()))>::type>::type;
+
+/// Given a range of type R, iterate the entire range and return a
+/// SmallVector with elements of the vector. This is useful, for example,
+/// when you want to iterate a range and then sort the results.
+template <unsigned Size, typename R>
+SmallVector<ValueTypeFromRangeType<R>, Size> to_vector(R&& Range) {
+ return { std::begin(Range), std::end(Range) };
+}
+template <typename R>
+SmallVector<ValueTypeFromRangeType<R>,
+ CalculateSmallVectorDefaultInlinedElements<
+ ValueTypeFromRangeType<R>>::value>
+to_vector(R&& Range) {
+ return { std::begin(Range), std::end(Range) };
+}
+
+namespace std {
+
+/// Implement std::swap in terms of SmallVector swap.
+template <typename T>
+inline void swap(SmallVectorImpl<T>& LHS, SmallVectorImpl<T>& RHS) {
+ LHS.swap(RHS);
+}
+
+/// Implement std::swap in terms of SmallVector swap.
+template <typename T, unsigned N>
+inline void swap(SmallVector<T, N>& LHS, SmallVector<T, N>& RHS) {
+ LHS.swap(RHS);
+}
+
+} // namespace std
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
diff --git a/source/10-common/StbImplementations.c b/source/10-common/StbImplementations.c
new file mode 100644
index 0000000..73bbc2a
--- /dev/null
+++ b/source/10-common/StbImplementations.c
@@ -0,0 +1,14 @@
+#define STB_RECT_PACK_IMPLEMENTATION
+#include <stb_rect_pack.h>
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include <stb_truetype.h>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+#define STB_SPRINTF_IMPLEMENTATION
+#include <stb_sprintf.h>
+
+#define STB_C_LEXER_IMPLEMENTATION
+#include <stb_c_lexer.h>
diff --git a/source/10-common/TypeTraits.hpp b/source/10-common/TypeTraits.hpp
new file mode 100644
index 0000000..cca9a1f
--- /dev/null
+++ b/source/10-common/TypeTraits.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+template <class T>
+struct DefaultDeleter {
+ void operator()(T* ptr) const {
+ delete ptr;
+ }
+};
+
+template <class>
+struct RemoveMemberPtrImpl {};
+
+template <class T, class U>
+struct RemoveMemberPtrImpl<U T::*> {
+ using Type = U;
+};
+
+template <class T>
+using RemoveMemberPtr = typename RemoveMemberPtrImpl<T>::Type;
diff --git a/source/10-common/Uid.cpp b/source/10-common/Uid.cpp
new file mode 100644
index 0000000..1930cd8
--- /dev/null
+++ b/source/10-common/Uid.cpp
@@ -0,0 +1,58 @@
+#include "Uid.hpp"
+
+#include "RapidJsonHelper.hpp"
+
+#include <rapidjson/document.h>
+#include <cstring>
+#include <random>
+
+Uid Uid::Create() {
+ std::random_device rd;
+ std::mt19937_64 gen(rd());
+ std::uniform_int_distribution<uint64_t> dist(
+ std::numeric_limits<uint64_t>::min(),
+ std::numeric_limits<uint64_t>::max());
+
+ Uid uid;
+ uid.upper = dist(gen);
+ uid.lower = dist(gen);
+ return uid;
+}
+
+bool Uid::IsNull() const {
+ return upper == 0 && lower == 0;
+}
+
+void Uid::ReadString(std::string_view str) {
+ sscanf(str.data(), BRUSSEL_Uid_SCAN_STR, &upper, &lower);
+}
+
+std::string Uid::WriteString() {
+ char buf[256];
+ snprintf(buf, sizeof(buf), BRUSSEL_Uid_FORMAT_STR, upper, lower);
+ return std::string(buf);
+}
+
+void Uid::Read(const rapidjson::Value& value) {
+ assert(value.IsArray());
+ assert(value.Size() == 2);
+ auto& upper = value[0];
+ assert(upper.IsUint64());
+ auto& lower = value[1];
+ assert(lower.IsUint64());
+
+ this->upper = upper.GetUint64();
+ this->lower = lower.GetUint64();
+}
+
+void Uid::WriteInto(rapidjson::Value& value, rapidjson::Document& root) {
+ value.Reserve(2, root.GetAllocator());
+ value.PushBack((uint64_t)upper, root.GetAllocator());
+ value.PushBack((uint64_t)lower, root.GetAllocator());
+}
+
+rapidjson::Value Uid::Write(rapidjson::Document& root) {
+ rapidjson::Value result(rapidjson::kArrayType);
+ WriteInto(result, root);
+ return result;
+}
diff --git a/source/10-common/Uid.hpp b/source/10-common/Uid.hpp
new file mode 100644
index 0000000..f58129c
--- /dev/null
+++ b/source/10-common/Uid.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Utils.hpp"
+
+#include <rapidjson/fwd.h>
+#include <cinttypes>
+#include <functional>
+#include <string>
+#include <string_view>
+
+#define BRUSSEL_Uid_SCAN_STR "%" PRIx64 "-%" PRIx64
+#define BRUSSEL_Uid_SCAN_EXPAND(uid) &((uid).upper), &((uid).upper)
+#define BRUSSEL_Uid_FORMAT_STR "%016" PRIx64 "-%016" PRIx64
+#define BRUSSEL_Uid_FORMAT_EXPAND(uid) (uid).upper, (uid).lower
+
+struct Uid {
+ uint64_t upper = 0;
+ uint64_t lower = 0;
+
+ static Uid Create();
+
+ bool IsNull() const;
+
+ void ReadString(std::string_view str);
+ std::string WriteString();
+
+ void Read(const rapidjson::Value& value);
+ void WriteInto(rapidjson::Value& value, rapidjson::Document& root);
+ rapidjson::Value Write(rapidjson::Document& root);
+
+ auto operator<=>(const Uid&) const = default;
+};
+
+template <>
+struct std::hash<Uid> {
+ size_t operator()(const Uid& uid) const {
+ size_t hash = 0;
+ Utils::HashCombine(hash, uid.upper);
+ Utils::HashCombine(hash, uid.lower);
+ return hash;
+ }
+};
diff --git a/source/10-common/Utils.cpp b/source/10-common/Utils.cpp
new file mode 100644
index 0000000..53b3863
--- /dev/null
+++ b/source/10-common/Utils.cpp
@@ -0,0 +1,87 @@
+#include "Utils.hpp"
+
+#ifdef _WIN32
+# include <Windows.h>
+#endif
+
+#ifdef _WIN32
+# define BRUSSEL_MODE_STRING(string) L##string
+#else
+# define BRUSSEL_MODE_STRING(string) string
+#endif
+
+#if _WIN32
+using FopenModeString = const wchar_t*;
+#else
+using FopenModeString = const char*;
+#endif
+
+static FopenModeString GetModeString(Utils::IoMode mode, bool binary) {
+ using namespace Utils;
+ if (binary) {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("rb");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("wb");
+ case WriteAppend: return BRUSSEL_MODE_STRING("ab");
+ }
+ } else {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("r");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("w");
+ case WriteAppend: return BRUSSEL_MODE_STRING("a");
+ }
+ }
+ return nullptr;
+}
+
+FILE* Utils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // std::filesystem::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively
+ // NOTE: _wfopen() only affects the type of path parameter, otherwise the file stream created is identical to the one by fopen()
+ return _wfopen(path.c_str(), ::GetModeString(mode, binary));
+#else
+ return fopen(path.c_str(), ::GetModeString(mode, binary));
+#endif
+}
+
+FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // On Windows, fopen() accepts ANSI codepage encoded path, convert our UTF-8 string to UTF-16 to ensure that no matter what the locale is, the path continues to work
+ WCHAR platformPath[MAX_PATH];
+ if (MultiByteToWideChar(CP_UTF8, 0, path, -1, platformPath, MAX_PATH) == 0) {
+ return nullptr;
+ }
+ return _wfopen(platformPath, ::GetModeString(mode, binary));
+#else
+ return fopen(path, ::GetModeString(mode, binary));
+#endif
+}
+
+bool Utils::InRangeInclusive(int n, int lower, int upper) {
+ if (lower > upper) {
+ std::swap(lower, upper);
+ }
+ return n >= lower && n <= upper;
+}
+
+bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) {
+ bool verticalLine = p1.x == p2.x && InRangeInclusive(candidate.x, p1.x, p2.x);
+ bool horizontalLine = p1.y == p2.y && InRangeInclusive(candidate.y, p1.y, p2.y);
+ return verticalLine && horizontalLine;
+}
+
+bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) {
+ return p1.x == p2.x || p1.y == p2.y;
+}
+
+std::string Utils::MakeRandomNumberedName(const char* tag) {
+ int n = std::rand();
+#define RNG_NAME_PATTERN "Unnamed %s #%d", tag, n
+ // NOTE: does not include null-terminator
+ int size = snprintf(nullptr, 0, RNG_NAME_PATTERN);
+ std::string result;
+ result.resize(size); // std::string::resize handles storage for null-terminator alreaedy
+ snprintf(result.data(), size, RNG_NAME_PATTERN);
+#undef RNG_NAME_PATTERN
+ return result;
+}
diff --git a/source/10-common/Utils.hpp b/source/10-common/Utils.hpp
new file mode 100644
index 0000000..9f28aad
--- /dev/null
+++ b/source/10-common/Utils.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <robin_hood.h>
+#include <cstdio>
+#include <cstring>
+#include <filesystem>
+#include <glm/glm.hpp>
+
+namespace Utils {
+
+enum IoMode {
+ Read,
+ WriteTruncate,
+ WriteAppend,
+};
+
+FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false);
+FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false);
+
+constexpr float Abs(float v) noexcept {
+ return v < 0.0f ? -v : v;
+}
+
+bool InRangeInclusive(int n, int lower, int upper);
+bool LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate);
+
+bool IsColinear(glm::ivec2 p1, glm::ivec2 p2);
+
+template <class T>
+void HashCombine(std::size_t& seed, const T& v) {
+ seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+}
+
+std::string MakeRandomNumberedName(const char* tag);
+
+} // namespace Utils
+
+struct StringHash {
+ using is_transparent = void;
+
+ std::size_t operator()(const std::string& key) const { return robin_hood::hash_bytes(key.c_str(), key.size()); }
+ std::size_t operator()(std::string_view key) const { return robin_hood::hash_bytes(key.data(), key.size()); }
+ std::size_t operator()(const char* key) const { return robin_hood::hash_bytes(key, std::strlen(key)); }
+};
+
+struct StringEqual {
+ using is_transparent = int;
+
+ bool operator()(std::string_view lhs, const std::string& rhs) const {
+ const std::string_view view = rhs;
+ return lhs == view;
+ }
+
+ bool operator()(const char* lhs, const std::string& rhs) const {
+ return std::strcmp(lhs, rhs.c_str()) == 0;
+ }
+
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ return lhs == rhs;
+ }
+};
diff --git a/source/10-common/YCombinator.hpp b/source/10-common/YCombinator.hpp
new file mode 100644
index 0000000..b1d2350
--- /dev/null
+++ b/source/10-common/YCombinator.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+template <class Func>
+struct YCombinator {
+ // NOTE: implicit constructor allows initializing this
+ Func func;
+
+ template <class... Ts>
+ decltype(auto) operator()(Ts&&... args) const {
+ // NOTE: static_cast<Ts>(args)... is equivalent to std::forward<Ts>(args)...
+ // written this way so that we don't have to include <utility>, as well as reducing template instanciations to help compile time
+ return func(*this, static_cast<Ts>(args)...);
+ }
+};
diff --git a/source/20-codegen-compiler/CodegenConfig.hpp b/source/20-codegen-compiler/CodegenConfig.hpp
new file mode 100644
index 0000000..b9dc56c
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenDecl.cpp b/source/20-codegen-compiler/CodegenDecl.cpp
new file mode 100644
index 0000000..7cf21ce
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenDecl.hpp b/source/20-codegen-compiler/CodegenDecl.hpp
new file mode 100644
index 0000000..32d5445
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenInput.inl b/source/20-codegen-compiler/CodegenInput.inl
new file mode 100644
index 0000000..0809e7f
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenMacros.hpp b/source/20-codegen-compiler/CodegenMacros.hpp
new file mode 100644
index 0000000..84c9d09
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenOutput.inl b/source/20-codegen-compiler/CodegenOutput.inl
new file mode 100644
index 0000000..ff7b912
--- /dev/null
+++ b/source/20-codegen-compiler/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/source/20-codegen-compiler/CodegenUtils.inl b/source/20-codegen-compiler/CodegenUtils.inl
new file mode 100644
index 0000000..6feb654
--- /dev/null
+++ b/source/20-codegen-compiler/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{
+ // TODO we need to get the header name
+ .text = &R"""(
+// This file is generated. Any changes will be overidden when building.
+
+#include <cstddef>
+#include <cstdint>
+#include <frozen/string.h>
+#include <frozen/unordered_map.h>
+using namespace std::literals;
+ )"""[1],
+ });
+}
+
+} // namespace Utils
diff --git a/source/20-codegen-compiler/main.cpp b/source/20-codegen-compiler/main.cpp
new file mode 100644
index 0000000..b139515
--- /dev/null
+++ b/source/20-codegen-compiler/main.cpp
@@ -0,0 +1,761 @@
+#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);
+
+ // TODO see CMakeLists.txt for rationale, clean this up to be a proper citizen
+ Utils::WriteOutputFile(CodegenOutput{}, state.outputDir, filenameStem, ".gs.cpp"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);
+ }
+ }
+
+ // TODO do we even need these?
+ // Utils::WriteOutputFile(state.mainHeaderOutput, state.outputDir, "GeneratedCode.hpp"sv);
+ // Utils::WriteOutputFile(state.mainSourceOutput, state.outputDir, "GeneratedCode.cpp"sv);
+
+ return 0;
+}
diff --git a/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt b/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt
new file mode 100644
index 0000000..441d97c
--- /dev/null
+++ b/source/20-codegen-compiler/test/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/source/20-codegen-runtime/MacrosCodegen.hpp b/source/20-codegen-runtime/MacrosCodegen.hpp
new file mode 100644
index 0000000..6803023
--- /dev/null
+++ b/source/20-codegen-runtime/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/20-codegen-runtime/Metadata.cpp b/source/20-codegen-runtime/Metadata.cpp
new file mode 100644
index 0000000..ee32054
--- /dev/null
+++ b/source/20-codegen-runtime/Metadata.cpp
@@ -0,0 +1 @@
+#include "Metadata.hpp"
diff --git a/source/20-codegen-runtime/Metadata.hpp b/source/20-codegen-runtime/Metadata.hpp
new file mode 100644
index 0000000..a038c15
--- /dev/null
+++ b/source/20-codegen-runtime/Metadata.hpp
@@ -0,0 +1,4 @@
+#pragma once
+
+#include "MacrosCodegen.hpp"
+#include "MetadataBase.hpp"
diff --git a/source/20-codegen-runtime/MetadataBase.cpp b/source/20-codegen-runtime/MetadataBase.cpp
new file mode 100644
index 0000000..3ccf870
--- /dev/null
+++ b/source/20-codegen-runtime/MetadataBase.cpp
@@ -0,0 +1 @@
+#include "MetadataBase.hpp"
diff --git a/source/20-codegen-runtime/MetadataBase.hpp b/source/20-codegen-runtime/MetadataBase.hpp
new file mode 100644
index 0000000..8be668d
--- /dev/null
+++ b/source/20-codegen-runtime/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/App.cpp b/source/30-game/App.cpp
index 45a7545..45a7545 100644
--- a/source/App.cpp
+++ b/source/30-game/App.cpp
diff --git a/source/App.hpp b/source/30-game/App.hpp
index c73c5a1..c73c5a1 100644
--- a/source/App.hpp
+++ b/source/30-game/App.hpp
diff --git a/source/AppConfig.hpp b/source/30-game/AppConfig.hpp
index 794bee5..794bee5 100644
--- a/source/AppConfig.hpp
+++ b/source/30-game/AppConfig.hpp
diff --git a/source/Camera.cpp b/source/30-game/Camera.cpp
index 39f0369..39f0369 100644
--- a/source/Camera.cpp
+++ b/source/30-game/Camera.cpp
diff --git a/source/Camera.hpp b/source/30-game/Camera.hpp
index 7bf0a6c..7bf0a6c 100644
--- a/source/Camera.hpp
+++ b/source/30-game/Camera.hpp
diff --git a/source/CommonVertexIndex.cpp b/source/30-game/CommonVertexIndex.cpp
index 786274e..786274e 100644
--- a/source/CommonVertexIndex.cpp
+++ b/source/30-game/CommonVertexIndex.cpp
diff --git a/source/CommonVertexIndex.hpp b/source/30-game/CommonVertexIndex.hpp
index adb81b6..adb81b6 100644
--- a/source/CommonVertexIndex.hpp
+++ b/source/30-game/CommonVertexIndex.hpp
diff --git a/source/EditorAccessories.cpp b/source/30-game/EditorAccessories.cpp
index 08d08ec..08d08ec 100644
--- a/source/EditorAccessories.cpp
+++ b/source/30-game/EditorAccessories.cpp
diff --git a/source/EditorAccessories.hpp b/source/30-game/EditorAccessories.hpp
index 687b509..687b509 100644
--- a/source/EditorAccessories.hpp
+++ b/source/30-game/EditorAccessories.hpp
diff --git a/source/EditorAttachment.hpp b/source/30-game/EditorAttachment.hpp
index 61b824b..61b824b 100644
--- a/source/EditorAttachment.hpp
+++ b/source/30-game/EditorAttachment.hpp
diff --git a/source/EditorAttachmentImpl.cpp b/source/30-game/EditorAttachmentImpl.cpp
index 62d15eb..62d15eb 100644
--- a/source/EditorAttachmentImpl.cpp
+++ b/source/30-game/EditorAttachmentImpl.cpp
diff --git a/source/EditorAttachmentImpl.hpp b/source/30-game/EditorAttachmentImpl.hpp
index 53bcd37..53bcd37 100644
--- a/source/EditorAttachmentImpl.hpp
+++ b/source/30-game/EditorAttachmentImpl.hpp
diff --git a/source/EditorCommandPalette.cpp b/source/30-game/EditorCommandPalette.cpp
index 0e7b894..0e7b894 100644
--- a/source/EditorCommandPalette.cpp
+++ b/source/30-game/EditorCommandPalette.cpp
diff --git a/source/EditorCommandPalette.hpp b/source/30-game/EditorCommandPalette.hpp
index 101344d..101344d 100644
--- a/source/EditorCommandPalette.hpp
+++ b/source/30-game/EditorCommandPalette.hpp
diff --git a/source/EditorCore.hpp b/source/30-game/EditorCore.hpp
index 726f43e..726f43e 100644
--- a/source/EditorCore.hpp
+++ b/source/30-game/EditorCore.hpp
diff --git a/source/EditorCorePrivate.cpp b/source/30-game/EditorCorePrivate.cpp
index 9fd6087..9fd6087 100644
--- a/source/EditorCorePrivate.cpp
+++ b/source/30-game/EditorCorePrivate.cpp
diff --git a/source/EditorCorePrivate.hpp b/source/30-game/EditorCorePrivate.hpp
index 4fbfb72..4fbfb72 100644
--- a/source/EditorCorePrivate.hpp
+++ b/source/30-game/EditorCorePrivate.hpp
diff --git a/source/EditorGuizmo.cpp b/source/30-game/EditorGuizmo.cpp
index 3e4f890..3e4f890 100644
--- a/source/EditorGuizmo.cpp
+++ b/source/30-game/EditorGuizmo.cpp
diff --git a/source/EditorGuizmo.hpp b/source/30-game/EditorGuizmo.hpp
index 0560050..0560050 100644
--- a/source/EditorGuizmo.hpp
+++ b/source/30-game/EditorGuizmo.hpp
diff --git a/source/EditorNotification.cpp b/source/30-game/EditorNotification.cpp
index e4a869e..e4a869e 100644
--- a/source/EditorNotification.cpp
+++ b/source/30-game/EditorNotification.cpp
diff --git a/source/EditorNotification.hpp b/source/30-game/EditorNotification.hpp
index 3af8c2d..3af8c2d 100644
--- a/source/EditorNotification.hpp
+++ b/source/30-game/EditorNotification.hpp
diff --git a/source/EditorUtils.cpp b/source/30-game/EditorUtils.cpp
index 20caef7..20caef7 100644
--- a/source/EditorUtils.cpp
+++ b/source/30-game/EditorUtils.cpp
diff --git a/source/EditorUtils.hpp b/source/30-game/EditorUtils.hpp
index 99c522b..99c522b 100644
--- a/source/EditorUtils.hpp
+++ b/source/30-game/EditorUtils.hpp
diff --git a/source/FuzzyMatch.cpp b/source/30-game/FuzzyMatch.cpp
index 0ab604d..0ab604d 100644
--- a/source/FuzzyMatch.cpp
+++ b/source/30-game/FuzzyMatch.cpp
diff --git a/source/FuzzyMatch.hpp b/source/30-game/FuzzyMatch.hpp
index 7a26b7e..7a26b7e 100644
--- a/source/FuzzyMatch.hpp
+++ b/source/30-game/FuzzyMatch.hpp
diff --git a/source/GameObject.cpp b/source/30-game/GameObject.cpp
index 8bb3ec7..8bb3ec7 100644
--- a/source/GameObject.cpp
+++ b/source/30-game/GameObject.cpp
diff --git a/source/GameObject.hpp b/source/30-game/GameObject.hpp
index 77488b9..77488b9 100644
--- a/source/GameObject.hpp
+++ b/source/30-game/GameObject.hpp
diff --git a/source/GraphicsTags.cpp b/source/30-game/GraphicsTags.cpp
index 522a58f..522a58f 100644
--- a/source/GraphicsTags.cpp
+++ b/source/30-game/GraphicsTags.cpp
diff --git a/source/GraphicsTags.hpp b/source/30-game/GraphicsTags.hpp
index f83b99c..f83b99c 100644
--- a/source/GraphicsTags.hpp
+++ b/source/30-game/GraphicsTags.hpp
diff --git a/source/Image.cpp b/source/30-game/Image.cpp
index 3673acc..3673acc 100644
--- a/source/Image.cpp
+++ b/source/30-game/Image.cpp
diff --git a/source/Image.hpp b/source/30-game/Image.hpp
index c577c24..c577c24 100644
--- a/source/Image.hpp
+++ b/source/30-game/Image.hpp
diff --git a/source/Ires.cpp b/source/30-game/Ires.cpp
index 10a6867..10a6867 100644
--- a/source/Ires.cpp
+++ b/source/30-game/Ires.cpp
diff --git a/source/Ires.hpp b/source/30-game/Ires.hpp
index 83ca175..83ca175 100644
--- a/source/Ires.hpp
+++ b/source/30-game/Ires.hpp
diff --git a/source/Level.cpp b/source/30-game/Level.cpp
index 5881084..5881084 100644
--- a/source/Level.cpp
+++ b/source/30-game/Level.cpp
diff --git a/source/Level.hpp b/source/30-game/Level.hpp
index c1170a3..c1170a3 100644
--- a/source/Level.hpp
+++ b/source/30-game/Level.hpp
diff --git a/source/Material.cpp b/source/30-game/Material.cpp
index e648970..e648970 100644
--- a/source/Material.cpp
+++ b/source/30-game/Material.cpp
diff --git a/source/Material.hpp b/source/30-game/Material.hpp
index f1cd7dd..f1cd7dd 100644
--- a/source/Material.hpp
+++ b/source/30-game/Material.hpp
diff --git a/source/Mesh.cpp b/source/30-game/Mesh.cpp
index 244e2e3..244e2e3 100644
--- a/source/Mesh.cpp
+++ b/source/30-game/Mesh.cpp
diff --git a/source/Mesh.hpp b/source/30-game/Mesh.hpp
index f86fd55..f86fd55 100644
--- a/source/Mesh.hpp
+++ b/source/30-game/Mesh.hpp
diff --git a/source/Player.cpp b/source/30-game/Player.cpp
index 34c4549..34c4549 100644
--- a/source/Player.cpp
+++ b/source/30-game/Player.cpp
diff --git a/source/Player.hpp b/source/30-game/Player.hpp
index d003a25..d003a25 100644
--- a/source/Player.hpp
+++ b/source/30-game/Player.hpp
diff --git a/source/Renderer.cpp b/source/30-game/Renderer.cpp
index 3497449..3497449 100644
--- a/source/Renderer.cpp
+++ b/source/30-game/Renderer.cpp
diff --git a/source/Renderer.hpp b/source/30-game/Renderer.hpp
index 98a9f28..98a9f28 100644
--- a/source/Renderer.hpp
+++ b/source/30-game/Renderer.hpp
diff --git a/source/SceneThings.cpp b/source/30-game/SceneThings.cpp
index 3fa0436..3fa0436 100644
--- a/source/SceneThings.cpp
+++ b/source/30-game/SceneThings.cpp
diff --git a/source/SceneThings.hpp b/source/30-game/SceneThings.hpp
index c261fbb..c261fbb 100644
--- a/source/SceneThings.hpp
+++ b/source/30-game/SceneThings.hpp
diff --git a/source/Shader.cpp b/source/30-game/Shader.cpp
index 48881f0..48881f0 100644
--- a/source/Shader.cpp
+++ b/source/30-game/Shader.cpp
diff --git a/source/Shader.hpp b/source/30-game/Shader.hpp
index 707e6cc..707e6cc 100644
--- a/source/Shader.hpp
+++ b/source/30-game/Shader.hpp
diff --git a/source/Sprite.cpp b/source/30-game/Sprite.cpp
index 2b4923c..2b4923c 100644
--- a/source/Sprite.cpp
+++ b/source/30-game/Sprite.cpp
diff --git a/source/Sprite.hpp b/source/30-game/Sprite.hpp
index e163a01..e163a01 100644
--- a/source/Sprite.hpp
+++ b/source/30-game/Sprite.hpp
diff --git a/source/Texture.cpp b/source/30-game/Texture.cpp
index 6fa7c8a..6fa7c8a 100644
--- a/source/Texture.cpp
+++ b/source/30-game/Texture.cpp
diff --git a/source/Texture.hpp b/source/30-game/Texture.hpp
index 108dfa7..108dfa7 100644
--- a/source/Texture.hpp
+++ b/source/30-game/Texture.hpp
diff --git a/source/VertexIndex.cpp b/source/30-game/VertexIndex.cpp
index ac68289..ac68289 100644
--- a/source/VertexIndex.cpp
+++ b/source/30-game/VertexIndex.cpp
diff --git a/source/VertexIndex.hpp b/source/30-game/VertexIndex.hpp
index 2d65617..2d65617 100644
--- a/source/VertexIndex.hpp
+++ b/source/30-game/VertexIndex.hpp
diff --git a/source/World.cpp b/source/30-game/World.cpp
index d4a8344..d4a8344 100644
--- a/source/World.cpp
+++ b/source/30-game/World.cpp
diff --git a/source/World.hpp b/source/30-game/World.hpp
index 288142e..288142e 100644
--- a/source/World.hpp
+++ b/source/30-game/World.hpp
diff --git a/source/main.cpp b/source/30-game/main.cpp
index c49fc0b..c49fc0b 100644
--- a/source/main.cpp
+++ b/source/30-game/main.cpp