Clang-based code generator that automatically produces boilerplate from annotated C++ symbols. Eliminates manual registration and binding code for game engines by parsing AST and generating custom output based on developer annotations.
Game engines require extensive boilerplate: ECS system registration, Lua binding generation, component inspector integration, and serialization code. Developers spend significant time writing repetitive registration functions and keeping them synchronized with class definitions.
Clang-powered tool that parses C++ source files, extracts annotated symbols, and generates all required boilerplate automatically. Developers annotate classes once with simple macros, and the system handles everything else at build time.
All reflection and code generation happens at compile time. Annotation macros compile to nothing, leaving zero runtime overhead while providing maximum development convenience.
Leverages Clang's powerful Abstract Syntax Tree parsing to achieve 100% accurate C++ symbol extraction, supporting complex templates and modern C++ features.
Generates custom boilerplate for ECS registration, Lua bindings, component inspector integration, and any other repetitive patterns your engine requires.
Six intuitive macros (CGCLASS, CGMEMBER, CGMETHOD, etc.) that compile to nothing but provide rich metadata for code generation without polluting your codebase.
Runs as a pre-build step, taking your project files and output directory as arguments. Seamlessly integrates with any build system that can run executables.
Specifically designed for game engine development patterns: ECS systems, component inspection, Lua scripting integration, and automatic registration workflows.
The system uses Clang's LibTooling to parse C++ source files, extract annotated symbols, and generate custom boilerplate code.
// Include the macro definitions (compile to nothing)
#include "CodeGenerator.hpp"
CGCLASS(LuaType="Player", LuaComponent, CanAccessComponents)
class Player {
public:
CGCONSTRUCTOR(LuaConstructor)
Player(float health, float speed) : health_(health), speed_(speed) {}
CGMETHOD(LuaFunction)
void TakeDamage(float amount) {
health_ -= amount;
if (health_ <= 0) Die();
}
CGVARIABLE(LuaVariable)
float health_;
CGVARIABLE(LuaEntity)
EntityID entity_id_;
private:
CGVARIABLE(LuaVariable)
float speed_;
void Die() { /* implementation */ }
};
The core of the system uses Clang's LibTooling to traverse the Abstract Syntax Tree and extract annotated symbols:
int main(int argc, char *argv[]) {
// Parse Visual Studio project file to get headers
XmlParser xmlParser{solution_file};
const auto headers = xmlParser.GetAllHeaders();
// Configure Clang arguments
std::vector<std::string> args{};
args.emplace_back("clang");
args.emplace_back("-fsyntax-only"); // AST only, no compilation
args.emplace_back("-std=c++17");
args.emplace_back("-IC:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.38.33130/include");
// Create compilation database and run Clang tool
SimpleOptionParser optionParser{args, headers};
clang::tooling::ClangTool tool{optionParser, optionParser.getAllFiles()};
// Reserve space for AST parsers
ASTFileParser::Reserve(headers.size());
// Parse all files and extract symbols
tool.run(clang::tooling::newFrontendActionFactory<ASTFrontendAction>().get());
// Generate code based on extracted symbols
FileGenerator generator{};
generator.ParseClass = HandleClass;
generator.ParseMember = HandleMember;
generator.ParseMethod = HandleMethod;
for (const auto &parser: ASTFileParser::GetParsers()) {
generator.Parse(parser);
}
// Write generated files
FileGenerator::WriteFiles();
return 0;
}
The system automatically generates different types of boilerplate based on the annotations it finds.
void HandleClass(FileGenerator &fileGenerator, const Class &class_) {
// Automatically register ECS systems
if (FileGenerator::GetProperty(class_.properties, "System") != nullptr) {
auto& file = fileGenerator.files["_Gen_Engine.cpp"];
file.includes.insert("core/engine.hpp");
file.includes.insert(class_.path);
auto& function = file.functions["bee::EngineClass::GeneratedInitialize"];
std::string line = "ecs_->AddSystem<" + class_.fullNamespace + ">();";
function.body.emplace_back(line);
}
// Register components with inspector
auto property = FileGenerator::GetProperty(class_.properties, "InspectableComponent");
if (property != nullptr) {
auto& file = fileGenerator.files["_Gen_Engine.cpp"];
file.includes.insert("core/engine.hpp");
file.includes.insert(class_.path);
auto& function = file.functions["bee::EngineClass::GeneratedInitialize"];
std::string line = "ecs_->RegisterComponent<" + class_.fullNamespace + ">(\"" + class_.name + "\");";
function.body.emplace_back(line);
}
}
// Generate Lua type registration
property = FileGenerator::GetProperty(class_.properties, "LuaType");
if (property != nullptr) {
auto& source = fileGenerator.files["_Gen_LuaBindings.cpp"];
auto& function = source.functions["CreateLuaBindings_Gen_"];
std::string tableName = "L";
// Register in component table if it's a component
if (FileGenerator::GetProperty(class_.properties, "LuaComponent") != nullptr) {
tableName = "componentTable";
static bool first = true;
if (first) {
first = false;
function.body.emplace_back("sol::table componentTable = L.create_table(\"Components\");");
}
}
// Create usertype binding
std::string line = "sol::usertype<" + class_.fullNamespace + "> " +
std::get<std::string>(property->value) + " = " + tableName +
".new_usertype<" + class_.fullNamespace + ">(\"" +
std::get<std::string>(property->value) + "\");";
function.body.emplace_back(line);
// Generate constructor bindings
std::deque<const Function*> constructors;
for (auto& member : class_.functions) {
if (FileGenerator::GetProperty(member.properties, "LuaConstructor") != nullptr){
constructors.emplace_back(&member);
}
}
// Bind constructors
if (!constructors.empty()) {
line = std::get<std::string>(property->value) + "[sol::meta_function::construct] = sol::constructors<";
for (const auto& constructor : constructors) {
line += constructor->GetNamespace() + "(/* parameters */);";
}
function.body.emplace_back(line);
}
}
The system provides six core macros for annotating C++ symbols:
// These macros compile to nothing - zero runtime cost
#define CGCLASS(...) // Annotate classes
#define CGMEMBER(...) // Annotate class members
#define CGMETHOD(...) // Annotate methods
#define CGCONSTRUCTOR(...)// Annotate constructors
#define CGVARIABLE(...) // Annotate variables
#define CGFUNCTION(...) // Annotate functions
LuaType: Register class in Lua
System: Auto-register ECS system
InspectableComponent: Add to inspector
CanAccessComponents: Generate component accessors
LuaFunction: Expose to Lua scripting
LuaConstructor: Enable Lua instantiation
Custom properties can be added for specific code generation needs
LuaVariable: Expose variable to Lua
LuaEntity: Mark as entity reference
Future: Serialization, editor exposure, validation
Properties are completely customizable. The HandleClass/HandleMethod functions can be modified to generate any type of boilerplate based on custom properties.
The code generator builds as a standalone executable and integrates with any build system:
cmake_minimum_required(VERSION 3.20)
project(CodeGenerator)
set(CMAKE_CXX_STANDARD 17)
# Define source files
set(SOURCE_FILES
src/XmlParser.cpp
src/FileGenerator.cpp
src/FileParser.cpp
src/SimpleOptionParser.cpp
main.cpp
)
add_executable(CodeGenerator ${SOURCE_FILES})
# Add Clang/LLVM as subdirectory
set(LLVM_ENABLE_PROJECTS "clang" CACHE STRING "" FORCE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/External/llvm/llvm)
# Link against Clang tooling
target_link_libraries(CodeGenerator PRIVATE clangTooling pugixml)
# Include Clang headers
target_include_directories(CodeGenerator PRIVATE
${LLVM_EXTERNAL_CLANG_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/External/llvm/llvm/tools/clang/include
${CMAKE_CURRENT_SOURCE_DIR}/External/llvm/llvm/include
)
# Run code generation before build
./CodeGenerator.exe MyProject.vcxproj ./generated_output/
# Generated files are ready for compilation
# _Gen_Engine.cpp - ECS system registration
# _Gen_LuaBindings.cpp - Lua type bindings
# _Gen_LuaBindings.hpp - Lua binding headers
The current system successfully demonstrates Clang AST parsing, symbol extraction, and code generation for ECS and Lua binding use cases. It's functional and ready for integration into game engine projects that need automated boilerplate generation.
Working on a more flexible redesign that exposes extracted symbols to scripting languages, allowing users to customize generated output without recompiling the tool. This will enable broader applications beyond the current ECS/Lua focus.
This project represents a deep dive into compiler technology and practical code generation. The technical blog provides detailed implementation notes and lessons learned from building a Clang-based tool for game engine development.