-
Notifications
You must be signed in to change notification settings - Fork 10
CodeGenModule
The CodeGenModule
class' role is to generate code for a given entity and CodeGenEnv
. It can also contain a bunch of PropertyCodeGen.
The first thing to understand when writing a module, is that we usually write a module for a specific CodeGenEnv
class. Indeed, the CodeGenEnv
contains data that might be needed for the module to work. When registering our module to a CodeGenUnit
, we must make sure that the CodeGenUnit::createCodeGenEnv
method indeed returns a CodeGenEnv of the targeted type.
First, let's create a new module class. For the sake of the example, we will create a module that works with the ExampleCodeGenUnit/ExampleCodeGenEnv we wrote in the previous section.
#include <Kodgen/CodeGen/CodeGenModule.h>
class ExampleCodeGenModule : public kodgen::CodeGenModule
{
};
Note: Some
CodeGenUnit
classes require to inherit from a different class when creating a module. Make sure to refer to the usedCodeGenUnit
documentation.
To register some PropertyCodeGen to the module, you just have to call the CodeGenModule::addPropertyCodeGen
method in your module constructors. It is important that they are both added in the default constructor as well as the copy constructor:
#include <Kodgen/CodeGen/CodeGenModule.h>
#include "ExamplePropertyCodeGen.h"
class ExampleCodeGenModule : public kodgen::CodeGenModule
{
private:
ExamplePropertyCodeGen _propertyCodeGen;
public:
ExampleCodeGenModule() noexcept
{
addPropertyCodeGen(_propertyCodeGen);
}
ExampleCodeGenModule(ExampleCodeGenModule const&) noexcept:
ExampleCodeGenModule() //Call default constructor to add the propertyCodeGen
{
//If for any reason you don't want to/can't call the default constructor
//You can just call again addPropertyCodeGen(_propertyCodeGen); here
}
};
The CodeGenModule
class implements the ICloneable
interface, used to make clones of a module and let each clone run on a different thread. The implementation is as simple as writing:
ICloneable* clone() const noexcept override
{
return new ExampleCodeGenModule(*this);
}
We just have to define the copy constructor if there is anything that is not trivially copiable.
If our CodeGenModule
contains one or more PropertyCodeGen as well, make sure the copy adds its own PropertyCodeGen references as specified in the Register PropertyCodeGen section.
The getGenerationOrder
method returns an integer defining in which order different code generation modules should execute. Modules with a low generation order will execute first, and modules with a high generation order will execute last. Modules having the same generation order value will execute in an undefined order. The default value is 0, so you don't need to override this method if the generation order for your module doesn't matter.
kodgen::int32 getGenerationOrder() const noexcept override
{
//initialGenerateCode, generateCodeForEntity and finalGenerateCode for this class will run after default modules
return 1;
}
The initialGenerateCode
method is called once at the very beginning of the generation process. It can be used to generate code that needs to be included only once per file. You can also use this method to check that the generation settings for your code gen module is valid. If it's not, return false, in which case the generation process for the running CodeGenUnit
is aborted. Try to log an explicit error that contains the reason of abort:
bool initialGenerateCode(kodgen::CodeGenEnv& env, std::string& inout_result) noexcept override
{
if (/* some problem occurs */)
{
//Log error message
if (env.getLogger() != nullptr)
env.getLogger()->log("Explicit error description.", kodgen::ILogger::ELogSeverity::Error);
return false;
}
//Generate some code...
inout_result += "Start generating code...";
return true;
}
The finalGenerateCode
method is called once after generateCodeForEntity
has been called on each entity of the parsed file. You can return false to abort code generation if anything is wrong. Try to log an explicit error that contains the reason of abort:
bool finalGenerateCode(kodgen::CodeGenEnv& env, std::string& inout_result) noexcept override
{
if (/* something went wrong */)
{
if (env.getLogger() != nullptr)
env.getLogger()->log("Explicit error description.", kodgen::ILogger::ELogSeverity::Error);
return false;
}
//Generate some final code...
inout_result += "End code generation.";
return true;
}
The generateCodeForEntity
method is called for each parsed entity. The code generated for the passed entity and environment must be appended to the inout_result parameter:
kodgen::ETraversalBehaviour generateCodeForEntity(kodgen::EntityInfo const& entity, kodgen::CodeGenEnv& env, std::string& inout_result) noexcept override
{
//Generate some code
inout_result += "Add the entity " + entity.name + " to the generated code";
//Return the combination of this method result + the base method result
return kodgen::ETraversalBehaviour::Recurse;
}
The result of this method determines how the entities should be traversed. The entities are traversed like an AST (Abstract Syntax Tree). To understand it a bit better, here is a basic AST:
File Foo.h
└─ Namespace ExampleNamespace1
| └─ Class Class1
| | └─ Field field1
| | └─ Field field2
| | └─ Field field3
| | └─ Method method1
| | └─ Method method2
| └─ Class Class2
| | └─ Field field1
| | └─ Field field2
└─ Namespace ExampleNamespace2
└─ Class Class3
We have here a file containing 2 namespaces and 1 class. 2 classes Class1 and Class2 are defined in the first namespace. Each of these 2 classes contains a bunch of fields and methods. The CodeGenUnit
will first call CodeGenModule::generateCodeForEntity
with the first entity under file level, ExampleNamespace1. Then, depending on the returned ETraversalBehaviour
value, the next entity will be picked differently:
-
Recurse: Pick the first children of the current entity if any. If there are no children, pick the next entity at the same AST level. In our case, Class1 is the first child of the ExampleNamespace1 entity, so the next
CodeGenModule::generateCodeForEntity
call will be fed with the Class1 entity; -
Continue: Pick the next entity at the same AST level. In our case, ExampleNamespace2 is the next entity, so the next
CodeGenModule::generateCodeForEntity
call will be fed with the ExampleNamespace2 entity; - Break: Pick the next entity of a different kind at the same AST level. In our case, ExampleNamespace2 is skipped because it is a namespace just like the processed entity (ExampleNamespace1), so we jump to Class3;
-
AbortWithSuccess: Terminate the traversal of the AST with success.
CodeGenModule::generateCodeForEntity
will not be called anymore by theCodeGenUnit
until the generation for a new file begins; - AbortWithFailure: Terminate the traversal of the AST and abort the code generation process for the current generation unit because something went wrong;
If we want to make a module that only generates code for structs and classes (including nested ones), we could write something like this:
kodgen::ETraversalBehaviour generateCodeForEntity(kodgen::EntityInfo const& entity, kodgen::CodeGenEnv& env, std::string& inout_result) noexcept override
{
if (entity.entityType == kodgen::EEntityType::Namespace)
{
//Don't generate code but recurse to get nested classes/structs
return kodgen::ETraversalBehaviour::Recurse;
}
else if (entity.entityType == kodgen::EEntityType::Class || entity.entityType == kodgen::EEntityType::Struct)
{
//Generate code here
//Recurse to make sure we catch nested classes and structs as well
return kodgen::ETraversalBehaviour::Recurse;
}
else
{
//Skip all entities until we get to a new namespace or struct/class
return kodgen::ETraversalBehaviour::Break;
}
}
Note: If you are not sure what you should return, just return
ETraversalBehaviour::Recurse
all the time and only generate code when you want to.
Once our CodeGenModule
class is ready, we need to register it to a CodeGenUnit instance to make it run and generate code. For that, you just have to use the CodeGenUnit::addModule
method:
ExampleCodeGenUnit codeGenUnit;
ExampleCodeGenModule codeGenModule;
codeGenUnit.addModule(codeGenModule);
The next call to CodeGenMgr::run
with this CodeGenUnit
will generate code with all registered modules.