-
Notifications
You must be signed in to change notification settings - Fork 23
Generate property specific code
By extending the code generator, user-defined properties have the possibility to generate and inject code into the target program.
For example, imagine you want to make a Slider property that can only be used on floats. Thanks to property code generation, we would generate a compile-time check to make sure any field having this property is indeed a float, and generate an explicit error message otherwise. Let's see in a few steps how it works and how we can use it.
Before we actually write anything, it is recommended to read the Kodgen README to better understand what we are going to use in the following steps.
- First of all, create a custom property in your program. Let's go through the full example with the Slider property stated above.
//Slider.h
#pragma once
#include <Refureku/TypeInfo/Properties/PropertySettings.h>
#include "Generated/Slider.rfk.h"
struct RFKStruct(PropertySettings(rfk::EEntityKind::Field | rfk::EEntityKind::Variable))
Slider : public rfk::Property
{
float min = 0.0f;
float max = 1.0f;
Slider(float _min, float _max) noexcept:
min{_min},
max{_max}
{}
Slider_GENERATED
};
File_GENERATED
- Then, we are going to extend the code generator to detect when our property is used, and make it generate code. In the generator code, let's create a new PropertyRule for our slider:
//SliderPropertyRule.h
#pragma once
//If your property hasn't any constructor taking parameters, include <RefurekuGenerator/Properties/DefaultSimplePropertyRule.h> instead
#include <RefurekuGenerator/Properties/DefaultComplexPropertyRule.h>
//If your property hasn't any constructor taking parameters, inherit from DefaultSimplePropertyRule instead
class SliderPropertyRule : public rfk::DefaultComplexPropertyRule
{
protected:
std::string generatePrePropertyAddCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenPropertyAddData& data) const noexcept override;
public:
SliderPropertyRule() noexcept;
};
- In the constructor definition, we just have to call the parent constructor with our property class name (wrote at step 1), and the targeted entities.
//SliderPropertyRule.cpp
#include "SliderPropertyRule.h"
SliderPropertyRule::SliderPropertyRule() noexcept:
rfk::DefaultComplexPropertyRule("Slider", kodgen::EEntityType::Field | kodgen::EEntityType::Variable)
{}
Doing so, whenever the parser goes through a property named Slider attached to a field or a variable, this property rule will be detected, allowing to generate code.
- Let's now implement the generatePrePropertyAddCode method to... generate code! The method takes 3 parameters:
- entity: The entity the property is attached to in the parsed code.
- property: The property detected by the property rule. In our case, it is the slider property;
- data: Data related to the generation context;
The string returned by the method is the generated code that will be inserted just before the property is added to the entity. Other overridable methods exist to generate code at different locations in the generated file.
//SliderPropertyRule.cpp
#include <Kodgen/InfoStructures/VariableInfo.h>
std::string SliderPropertyRule::generatePrePropertyAddCode(kodgen::EntityInfo const& entity, kodgen::ComplexProperty const& property, rfk::PropertyCodeGenPropertyAddData& data) const noexcept
{
//For the slider, we want to check that the entity type is a float at compile-time, so we will use a static_assert, which can be used pretty much anywhere
//We know that the entity is a field or a variable, so we can cast the entity parameter to the concrete type and get info from it
//FieldInfo inherits from VariableInfo so it's safe to cast entity to VariableInfo
kodgen::VariableInfo const& var = static_cast<kodgen::VariableInfo const&>(entity);
return "static_assert(std::is_same_v<float, " + var.type.getName() + ">, \"Slider can only be used on float fields and variables.\");";
}
Note: The returned string is usually inserted inside macros, so it is not safe to use the \n character as it will break the macro, hence breaking the whole generated code. The only location where it is safe to use \n is the FileHeader.
- Finally, we need to add the SliderPropertyRule to our FileParserFactory:
#pragma once
#include <RefurekuGenerator/Parsing/FileParserFactory.h>
#include <RefurekuGenerator/Parsing/FileParser.h>
#include "SliderPropertyRule.h"
class YourFileParserFactory : public rfk::FileParserFactory<rfk::FileParser>
{
private:
SliderPropertyRule _sliderPropertyRule;
public:
YourFileParserFactory() noexcept
{
//If your property is a simple property (property that only has a parameterless constructor),
//add the rule to parsingSettings.propertyParsingSettings.simplePropertyRules instead
parsingSettings.propertyParsingSettings.complexPropertyRules.emplace_back(&_sliderPropertyRule);
}
};
With a simple example like the Slider one, it is hard to demonstrate all the possibilities the properties code generation system can offer. In this section, we will uncover in more detail the information we have to generate code.
First, we overrided the following method in the example:
std::string generatePrePropertyAddCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenPropertyAddData& data) const noexcept override;
There are actually 4 more overridable methods that generate and inject code at different places in the generated file:
//Code is injected at the generated file include line (#include "File.rfk.h")
std::string generateFileHeaderCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenFileHeaderData& data) const noexcept override;
//Code is injected just after the property has been added to an entity.
//It is possible to generate code for the entity and/or the property in the target program
std::string generatePostPropertyAddCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenPropertyAddData& data) const noexcept override;
//Code is injected in the [ClassName]_GENERATED macro
//Only called when the entity is a struct, class, field or method
std::string generateClassFooterCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenClassFooterData& data) const noexcept override;
//Code is injected in the File_GENERATED macro
std::string generateFileFooterCode(kodgen::EntityInfo const& entity,
kodgen::ComplexProperty const& property,
rfk::PropertyCodeGenFileFooterData& data) const noexcept override;
Regarding the entity parameter, we wrote the following in the example:
kodgen::VariableInfo const& var = static_cast<kodgen::VariableInfo const&>(entity);
It is indeed possible to cast the entity to a more concrete type depending on the entity.entityType value. If your property targets a single type of entity, you can cast it right away to the type you expect:
- Struct / Class -> cast to kodgen::StructClassInfo const& (Kodgen/InfoStructures/StructClassInfo.h)
- Enum -> cast to kodgen::EnumInfo const& (Kodgen/InfoStructures/EnumInfo.h)
- EnumValue -> cast to kodgen::EnumValueInfo const& (Kodgen/InfoStructures/EnumValueInfo.h)
- Variable -> cast to kodgen::VariableInfo const& (Kodgen/InfoStructures/VariableInfo.h)
- Field -> cast to kodgen::FieldInfo const& (Kodgen/InfoStructures/FieldInfo.h)
- Function -> cast to kodgen::FunctionInfo const& (Kodgen/InfoStructures/FunctionInfo.h)
- Method -> cast to kodgen::MethodInfo const& (Kodgen/InfoStructures/MethodInfo.h)
- Namespace -> cast to kodgen::NamespaceInfo const& (Kodgen/InfoStructures/NamespaceInfo.h)