Was able to parse UObject with gencpp!!!

This commit is contained in:
Edward R. Gonzalez 2024-04-14 21:51:14 -04:00
parent 7b9e277bc1
commit 48d21ddd15
25 changed files with 855 additions and 422 deletions

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"autoHide.autoHideSideBar": false,
"autoHide.autoHidePanel": false
}

Binary file not shown.

View File

@ -4,6 +4,7 @@
GameDefaultMap=/Game/Levels/StartupMap.StartupMap GameDefaultMap=/Game/Levels/StartupMap.StartupMap
EditorStartupMap=/Game/Levels/StartupMap.StartupMap EditorStartupMap=/Game/Levels/StartupMap.StartupMap
GlobalDefaultGameMode=/Game/Core/Game/BP_GameMode.BP_GameMode_C GlobalDefaultGameMode=/Game/Core/Game/BP_GameMode.BP_GameMode_C
GameInstanceClass=/Script/Gasa.GasaGameInstance
[/Script/WindowsTargetPlatform.WindowsTargetSettings] [/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 DefaultGraphicsRHI=DefaultGraphicsRHI_DX12

Binary file not shown.

BIN
Project/Content/Levels/StartupMap.umap (Stored with Git LFS)

Binary file not shown.

View File

@ -21,7 +21,6 @@ public:
FGameplayAttributeData MaxMana; FGameplayAttributeData MaxMana;
UGasaAttributeSet(); UGasaAttributeSet();
UFUNCTION() UFUNCTION()
void Client_OnRep_Health( FGameplayAttributeData& PrevHealth ); void Client_OnRep_Health( FGameplayAttributeData& PrevHealth );
UFUNCTION() UFUNCTION()

View File

@ -3,7 +3,6 @@
#include "Camera/CameraComponent.h" #include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h" #include "GameFramework/SpringArmComponent.h"
ACameraMount::ACameraMount() ACameraMount::ACameraMount()
{ {
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;

View File

@ -22,4 +22,3 @@ public:
void PostInitializeComponents() override; void PostInitializeComponents() override;
#pragma endregion Actor #pragma endregion Actor
}; };

View File

@ -5,4 +5,7 @@ void UGasaGameInstance::Init()
Super::Init(); Super::Init();
DevOptionsCache.CachedDevOptions(); DevOptionsCache.CachedDevOptions();
using namespace Gasa;
Log(FString::Printf(TEXT("UObject Size: %d RT: %d"), sizeof(UObject), UObject::StaticClass()->PropertiesSize ));
} }

View File

@ -0,0 +1,9 @@
// Don't keep this included anywhere
// Purely for inspection purposes
#include "GasaCommon.h"
void test()
{
UObject::StaticClass()->PropertiesSize
}

View File

@ -0,0 +1,7 @@
#include "GasaUserWidget.h"
UGasaUserWidget::UGasaUserWidget(FObjectInitializer const& ObjectInitializer)
: UUserWidget(ObjectInitializer)
{
}

View File

@ -0,0 +1,26 @@
#pragma once
#include "Blueprint/UserWidget.h"
#include "GasaUserWidget.generated.h"
UCLASS(Blueprintable)
class GASA_API UGasaUserWidget : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UObject> WidgetController;
UGasaUserWidget(FObjectInitializer const& ObjectInitializer);
UFUNCTION(BlueprintCallable)
void SetWidgetController(UObject* Controller)
{
WidgetController = Controller;
OnWidgetControllerSet();
}
UFUNCTION(BlueprintImplementableEvent)
void OnWidgetControllerSet();
};

View File

@ -0,0 +1,9 @@
#pragma once
#include "Components/ProgressBar.h"
UCLASS()
class GASA_API UProgressIndicator : public UProgressBar
{
};

View File

@ -0,0 +1 @@
#include "WidgetController.h"

View File

@ -0,0 +1,23 @@
#pragma once
#include "GasaCommon.h"
#include "WidgetController.generated.h"
UCLASS(BlueprintType)
class GASA_API UWdgetController : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category="Player")
TObjectPtr<APlayerController> Controller;
UPROPERTY(BlueprintReadOnly, Category="Player")
TObjectPtr<APlayerState> PlayerState;
UPROPERTY(BlueprintReadOnly, Category="Player")
TObjectPtr<UAbilitySystemComponent> AbilitySystem;
UPROPERTY(BlueprintReadOnly, Category="Player")
TObjectPtr<UAttributeSet> Attributes;
};

View File

@ -5,280 +5,136 @@
#define GEN_IMPLEMENTATION #define GEN_IMPLEMENTATION
#include "gen.cpp" #include "gen.cpp"
#include "gen.builder.cpp" #include "gen.builder.cpp"
#include "gen.scanner.hpp"
using namespace gen; using namespace gen;
// Program assumes its working directory is the project #include "GasaGenCommon.cpp"
#define path_config "./Source/Config/" #include "GasaGen_UGasaAttributeSet.cpp"
#define path_module_gasa "./Source/Gasa/"
#define path_gasa_ability_system path_module_gasa "AbilitySystem/"
void def_attribute_properties( CodeBody body, Array<StringCached> properties )
{
for ( StringCached property : properties )
{
Code field_uproperty = code_fmt( "property", (StrC)property, stringize(
UPROPERTY(ReplicatedUsing=Client_OnRep_<property>, EditAnywhere, BlueprintReadWrite, Category="Attributes")
));
CodeType type_FGameplayAttributeData = def_type( txt("FGameplayAttributeData"));
body.append(fmt_newline);
body.append( field_uproperty );
body.append(fmt_newline);
body.append( def_variable( type_FGameplayAttributeData, StrC(property)) );
}
}
void def_attribute_field_on_reps( CodeBody body, Array<StringCached> fields )
{
for ( StringCached field : fields )
{
Code umeta_UFUNCTION = code_str( UFUNCTION() );
body.append(fmt_newline);
body.append( umeta_UFUNCTION );
body.append(fmt_newline);
body.append( code_fmt( "field", (StrC)field, stringize(
void Client_OnRep_<field>(FGameplayAttributeData& Prev<field>);
)));
}
}
void def_attribute_field_property_getters( CodeBody body, StrC class_name, Array<StringCached> properties )
{
for ( String property : properties )
{
CodeFn generated_get_attribute = parse_function(
token_fmt( "class_name", class_name, "property", (StrC)property,
stringize(
static FGameplayAttribute Get<property>Attribute()
{
static FProperty* Prop = FindFieldChecked<FProperty>(<class_name>::StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
return Prop;
}
)));
body.append( generated_get_attribute );
}
}
void def_attribute_field_value_getters( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
#pragma push_macro(FORCEINLINE)
#undef FORCEINLINE
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE float Get<property>() const
{
return <property>.GetCurrentValue();
}
)));
#pragma pop_macro(FORCEINLINE)
}
}
void def_attribute_field_value_setters( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE void Set<property>(float NewVal)
{
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
if (ensure(AbilityComp))
{
AbilityComp->SetNumericAttributeBase(Get<property>Attribute(), NewVal);
};
}
)));
}
}
void def_attribute_field_initers ( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE void Init<property>(float NewVal)
{
<property>.SetBaseValue(NewVal);
<property>.SetCurrentValue(NewVal);
}
)));
}
}
void impl_attribute_fields( CodeBody body, StrC class_name, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append(fmt_newline);
CodeFn field_impl = parse_function( token_fmt( "class_name", class_name, "property", (StrC)property,
stringize(
void <class_name>::Client_OnRep_<property>(FGameplayAttributeData& Prev<property>)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(<class_name>, <property>, Prev<property>)
}
)));
body.append( field_impl );
}
}
int gen_main() int gen_main()
{ {
gen::init(); gen::init();
log_fmt("Generating code for the Gasa module"); log_fmt("Generating code for the Gasa module");
Code umeta_uclass = code_str( UCLASS() ); // Initialize Globals
Code umeta_generated_body = code_str( GENERATED_BODY() ); umeta_uclass = code_str( UCLASS() );
Code gasa_api = code_str( GASA_API ); umeta_generated_body = code_str( GENERATED_BODY() );
gasa_api = code_str( GASA_API );
CodeType type_UAttributeSet = def_type( txt("UAttributeSet") ); StrC str_generated_body = txt("GENERATED_BODY(");
StrC str_generated_uclass_body = txt("GENERATED_UCLASS_BODY(");
StrC str_property_binding_impl = txt("PROPERTY_BINDING_IMPLEMENTATION");
StrC str_uclass = txt("UCLASS(");
StrC str_ue_deprecated = txt("UE_DEPRECATED(");
StrC str_ufunction = txt("UFUNCTION(");
StrC str_uproperty = txt("UPROPERTY(");
StrC str_umg_api = txt("UMG_API");
CodeComment generation_notice = def_comment(txt("This was generated by GasaGen/GasaGen.cpp")); StrC str_declare_log_category_extern = txt("DECLARE_LOG_CATEGORY_EXTERN(");
StrC str_enum_class_flags = txt("ENUM_CLASS_FLAGS(");
StrC str_declare_class = txt("DECLARE_CLASS(");
StrC str_define_default_object_initializer_constructor_call = txt("DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(");
StrC str_core_object_api = txt("COREUOBJECT_API");
StrC str_macro_text = txt("TEXT(");
StrC str_declare_multicast_delegate_one_parameter = txt("DECLARE_MULTICAST_DELEGATE_OneParam(");
StrC str_declare_multicast_delegate_two_parameter = txt("DECLARE_MULTICAST_DELEGATE_TwoParams(");
StrC str_declare_multicast_delegate_three_parameter = txt("DECLARE_MULTICAST_DELEGATE_ThreeParams(");
StrC str_declare_delegate_retval_one_param = txt("DECLARE_DELEGATE_RetVal_OneParam(");
StrC str_declare_function = txt("DECLARE_FUNCTION(");
StrC str_result_decl = txt("RESULT_DECL");
StrC str_property_binding_implementation = txt("PROPERTY_BINDING_IMPLEMENTATION(");
StrC str_FORCEINLINE = txt("FORCEINLINE");
Array<StringCached> attribute_fields = Array<StringCached>::init( GlobalAllocator); PreprocessorDefines.append( get_cached_string(str_generated_body));
attribute_fields.append( get_cached_string(txt("Health"))); PreprocessorDefines.append( get_cached_string(str_generated_uclass_body));
attribute_fields.append( get_cached_string(txt("MaxHealth"))); PreprocessorDefines.append( get_cached_string(str_property_binding_impl));
attribute_fields.append( get_cached_string(txt("Mana"))); PreprocessorDefines.append( get_cached_string(str_ue_deprecated));
attribute_fields.append( get_cached_string(txt("MaxMana"))); PreprocessorDefines.append( get_cached_string(str_uclass));
PreprocessorDefines.append( get_cached_string(str_ufunction));
PreprocessorDefines.append( get_cached_string(str_uproperty));
PreprocessorDefines.append( get_cached_string(str_umg_api));
PreprocessorDefines.append( get_cached_string(str_declare_log_category_extern));
PreprocessorDefines.append( get_cached_string(str_enum_class_flags));
PreprocessorDefines.append( get_cached_string(str_declare_class));
PreprocessorDefines.append( get_cached_string(str_define_default_object_initializer_constructor_call));
PreprocessorDefines.append( get_cached_string(str_core_object_api));
PreprocessorDefines.append( get_cached_string(str_macro_text));
PreprocessorDefines.append( get_cached_string(str_declare_multicast_delegate_one_parameter));
PreprocessorDefines.append( get_cached_string(str_declare_multicast_delegate_two_parameter));
PreprocessorDefines.append( get_cached_string(str_declare_multicast_delegate_three_parameter));
PreprocessorDefines.append( get_cached_string(str_declare_delegate_retval_one_param));
PreprocessorDefines.append( get_cached_string(str_declare_function));
PreprocessorDefines.append( get_cached_string(str_result_decl));
PreprocessorDefines.append( get_cached_string(str_property_binding_implementation));
PreprocessorDefines.append( get_cached_string(str_FORCEINLINE));
#define path_UProgressBar \
"C:/projects/Unreal/Surgo/UE/Engine/Source/Runtime/UMG/Public/Components/ProgressBar.h"
StrC attributeset_name = txt("UGasaAttributeSet"); FileContents content = file_read_contents( GlobalAllocator, true, path_UProgressBar );
CodeBody parsed_uprogressbar = parse_global_body( StrC { content.size, (char const*)content.data });
Builder header = Builder::open( path_gasa_ability_system "GasaAttributeSet.h"); log_fmt("\n\n");
for ( Code gcode : parsed_uprogressbar )
{ {
header.print(generation_notice); if ( gcode->Type == CodeT::Class )
header.print(fmt_newline);
{ {
CodeInclude Include_AttributeSet = def_include(txt("AttributeSet.h")); log_fmt("Class %S - Definitions:\n", gcode->Name);
CodeInclude Include_AbilitySystemComponent = def_include(txt("AbilitySystemComponent.h"));
CodeInclude Include_GasaAttributeSet_Generated = def_include(txt("GasaAttributeSet.generated.h"));
CodeAttributes attributes = def_attributes( gasa_api->Name); if (gcode->Body->Type != CodeT::Class_Body)
continue;
CodeClass GasaAttributeSet = {}; for ( Code class_code : gcode->Body->cast<CodeBody>() )
{ {
CodeBody body = def_body( CodeT::Class_Body ); switch ( class_code->Type )
{ {
body.append( umeta_generated_body); case CodeT::Variable:
body.append( fmt_newline); case CodeT::Function:
body.append( access_public ); case CodeT::Function_Fwd:
if ( class_code->Name )
def_attribute_properties( body, attribute_fields); {
log_fmt("%s\n", class_code->Name );
body.append(fmt_newline); }
body.append( def_constructor() ); break;
def_attribute_field_on_reps( body, attribute_fields);
body.append(fmt_newline);
body.append( fmt_newline );
body.append( def_pragma(code( region Getters )));
def_attribute_field_property_getters( body, attributeset_name, attribute_fields );
def_attribute_field_value_getters( body, attribute_fields );
body.append( def_pragma(code( endregion Getters )));
body.append( fmt_newline );
body.append( def_pragma(code( region Setters )));
def_attribute_field_value_setters( body, attribute_fields );
def_attribute_field_initers( body, attribute_fields );
body.append( def_pragma(code( endregion Setters )));
body.append( fmt_newline );
body.append( def_pragma( txt("region UObject")));
body.append( parse_function( code(
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
)));
body.append( def_pragma( txt("endregion UObject")));
} }
GasaAttributeSet = def_class( txt("UGasaAttributeSet"), body
, type_UAttributeSet
, AccessSpec::Public
, attributes
);
} }
CodeNS ns_gasa = parse_namespace( code(
namespace Gasa
{
inline
UGasaAttributeSet const* GetAttributeSet( UAbilitySystemComponent* ASC )
{
return Cast<UGasaAttributeSet>(ASC->GetAttributeSet( UGasaAttributeSet::StaticClass() ));
}
}
));
header.print( Include_AttributeSet);
header.print( Include_AbilitySystemComponent);
header.print( Include_GasaAttributeSet_Generated);
header.print( fmt_newline);
header.print(umeta_uclass);
header.print(GasaAttributeSet);
header.print(ns_gasa);
} }
header.write();
} }
Builder source = Builder::open( path_gasa_ability_system "GasaAttributeSet.cpp" ); #define path_UObject \
R"(C:\projects\Unreal\Surgo\UE\Engine\Source\Runtime\CoreUObject\Public\UObject\Object.h)"
content = file_read_contents( GlobalAllocator, true, path_UObject );
CodeBody parsed_uobject = parse_global_body( StrC { content.size, (char const*)content.data });
log_fmt("\n\n");
for ( Code gcode : parsed_uobject )
{ {
source.print(generation_notice); if ( gcode->Type == CodeT::Class )
source.print( def_include( txt("GasaAttributeSet.h")));
source.print(fmt_newline);
source.print( def_include( txt("AbilitySystemComponent.h")));
source.print( def_include( txt("Net/UnrealNetwork.h")));
source.print( def_include( txt("Networking/GasaNetLibrary.h")));
{ {
CodeBody body = def_body( CodeT::Global_Body ); log_fmt("Class %S - Definitions:\n", gcode->Name);
body.append(fmt_newline);
body.append(code_str(
UGasaAttributeSet::UGasaAttributeSet()
{
InitHealth( 100.f );
InitMaxHealth( 100.f );
InitMana(( 50.f ));
InitMaxMana( 50.f );
}
));
body.append(fmt_newline);
impl_attribute_fields( body, attributeset_name, attribute_fields); if (gcode->Body->Type != CodeT::Class_Body)
continue;
CodeFn GetLifetimeOfReplicatedProps; for ( Code class_code : gcode->Body->cast<CodeBody>() )
{ {
CodeBody field_lifetimes = def_body( CodeT::Function_Body); switch ( class_code->Type )
for (StringCached field : attribute_fields)
{ {
field_lifetimes.append( code_fmt( "field", (StrC)field, stringize( case CodeT::Variable:
DOREPLIFETIME_DEFAULT_GAS(UGasaAttributeSet, <field>); case CodeT::Function:
))); case CodeT::Function_Fwd:
if ( class_code->Name )
{
log_fmt("%s\n", class_code->Name );
}
break;
} }
GetLifetimeOfReplicatedProps = parse_function( token_fmt( "body", (StrC)(field_lifetimes.to_string()), stringize(
void UGasaAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
<body>
}
)));
} }
body.append(GetLifetimeOfReplicatedProps);
source.print(body);
} }
source.write();
} }
// gen::deinit(); StrC str_gasa_api = txt("GASA_API");
gen_UGasaAttributeSet();
return 0; return 0;
} }

View File

@ -0,0 +1,13 @@
#pragma once
// Program assumes its working directory is the project
#define path_config "./Source/Config/"
#define path_module_gasa "./Source/Gasa/"
#define path_gasa_ability_system path_module_gasa "AbilitySystem/"
#pragma region Globals
// These Code objects are created before anything else after gencpp does its initializatioon
global Code umeta_uclass;
global Code umeta_generated_body;
global Code gasa_api;
#pragma endregion Globals

View File

@ -0,0 +1,268 @@
// Used in the GasaGen.cpp translation unit
void def_attribute_properties ( CodeBody body, Array<StringCached> properties );
void def_attribute_field_on_reps ( CodeBody body, Array<StringCached> properties );
void def_attribute_field_property_getters( CodeBody body, StrC class_name, Array<StringCached> properties );
void def_attribute_field_value_getters ( CodeBody body, Array<StringCached> properties );
void def_attribute_field_value_setters ( CodeBody body, Array<StringCached> properties );
void def_attribute_field_initers ( CodeBody body, Array<StringCached> properties );
void impl_attribute_fields ( CodeBody body, StrC class_name, Array<StringCached> properties );
void gen_UGasaAttributeSet()
{
CodeType type_UAttributeSet = def_type( txt("UAttributeSet") );
CodeComment generation_notice = def_comment(txt("This was generated by GasaGen/GasaGen.cpp"));
Array<StringCached> attribute_fields = Array<StringCached>::init( GlobalAllocator);
attribute_fields.append( get_cached_string(txt("Health")));
attribute_fields.append( get_cached_string(txt("MaxHealth")));
attribute_fields.append( get_cached_string(txt("Mana")));
attribute_fields.append( get_cached_string(txt("MaxMana")));
StrC class_name = txt("UGasaAttributeSet");
Builder header = Builder::open( path_gasa_ability_system "GasaAttributeSet.h");
{
header.print(generation_notice);
header.print(fmt_newline);
{
CodeInclude Include_AttributeSet = def_include(txt("AttributeSet.h"));
CodeInclude Include_AbilitySystemComponent = def_include(txt("AbilitySystemComponent.h"));
CodeInclude Include_GasaAttributeSet_Generated = def_include(txt("GasaAttributeSet.generated.h"));
CodeAttributes api_attribute= def_attributes( gasa_api->Name);
CodeClass GasaAttributeSet = {};
{
CodeBody body = def_body( CodeT::Class_Body );
{
body.append( umeta_generated_body);
body.append( fmt_newline);
body.append( access_public );
def_attribute_properties( body, attribute_fields);
body.append(fmt_newline);
body.append( def_constructor() );
def_attribute_field_on_reps( body, attribute_fields);
body.append(fmt_newline);
body.append( fmt_newline );
body.append( def_pragma(code( region Getters )));
def_attribute_field_property_getters( body, class_name, attribute_fields );
def_attribute_field_value_getters( body, attribute_fields );
body.append( def_pragma(code( endregion Getters )));
body.append( fmt_newline );
body.append( def_pragma(code( region Setters )));
def_attribute_field_value_setters( body, attribute_fields );
def_attribute_field_initers( body, attribute_fields );
body.append( def_pragma(code( endregion Setters )));
body.append( fmt_newline );
body.append( def_pragma( txt("region UObject")));
body.append( parse_function( code(
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
)));
body.append( def_pragma( txt("endregion UObject")));
}
GasaAttributeSet = def_class( class_name, body
, type_UAttributeSet, AccessSpec::Public
, api_attribute
);
}
CodeNS ns_gasa = parse_namespace( code(
namespace Gasa
{
inline
UGasaAttributeSet const* GetAttributeSet( UAbilitySystemComponent* ASC )
{
return Cast<UGasaAttributeSet>(ASC->GetAttributeSet( UGasaAttributeSet::StaticClass() ));
}
}
));
header.print( Include_AttributeSet);
header.print( Include_AbilitySystemComponent);
header.print( Include_GasaAttributeSet_Generated);
header.print( fmt_newline);
header.print(umeta_uclass);
header.print(GasaAttributeSet);
header.print(ns_gasa);
}
header.write();
}
Builder source = Builder::open( path_gasa_ability_system "GasaAttributeSet.cpp" );
{
source.print(generation_notice);
header.print(fmt_newline);
source.print( def_include( txt("GasaAttributeSet.h")));
source.print(fmt_newline);
source.print( def_include( txt("AbilitySystemComponent.h")));
source.print( def_include( txt("Net/UnrealNetwork.h")));
source.print( def_include( txt("Networking/GasaNetLibrary.h")));
{
CodeBody body = def_body( CodeT::Global_Body );
body.append(fmt_newline);
CodeConstructor constructor_for_UGasaAttributeSet = parse_constructor( code(
UGasaAttributeSet::UGasaAttributeSet()
{
InitHealth( 100.f );
InitMaxHealth( 100.f );
InitMana(( 50.f ));
InitMaxMana( 50.f );
}
));
body.append(constructor_for_UGasaAttributeSet );
body.append(fmt_newline);
impl_attribute_fields( body, class_name, attribute_fields);
CodeFn GetLifetimeOfReplicatedProps;
{
CodeBody field_lifetimes = def_body( CodeT::Function_Body);
for (StringCached field : attribute_fields)
{
field_lifetimes.append( code_fmt( "field", (StrC)field, stringize(
DOREPLIFETIME_DEFAULT_GAS(UGasaAttributeSet, <field>);
)));
}
GetLifetimeOfReplicatedProps = parse_function( token_fmt( "body", (StrC)(field_lifetimes.to_string()), stringize(
void UGasaAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
<body>
}
)));
}
body.append(GetLifetimeOfReplicatedProps);
source.print(body);
}
source.write();
}
}
void def_attribute_properties( CodeBody body, Array<StringCached> properties )
{
for ( StringCached property : properties )
{
Code field_uproperty = code_fmt( "property", (StrC)property, stringize(
UPROPERTY(ReplicatedUsing=Client_OnRep_<property>, EditAnywhere, BlueprintReadWrite, Category="Attributes")
));
CodeType type_FGameplayAttributeData = def_type( txt("FGameplayAttributeData"));
body.append(fmt_newline);
body.append( field_uproperty );
body.append(fmt_newline);
body.append( def_variable( type_FGameplayAttributeData, StrC(property)) );
}
}
void def_attribute_field_on_reps( CodeBody body, Array<StringCached> properties )
{
for ( StringCached property : properties )
{
Code umeta_UFUNCTION = code_str( UFUNCTION() );
body.append(fmt_newline);
body.append( umeta_UFUNCTION );
body.append(fmt_newline);
body.append( code_fmt( "property", (StrC)property, stringize(
void Client_OnRep_<property>(FGameplayAttributeData& Prev<property>);
)));
}
}
void def_attribute_field_property_getters( CodeBody body, StrC class_name, Array<StringCached> properties )
{
for ( String property : properties )
{
CodeFn generated_get_attribute = parse_function(
token_fmt( "class_name", class_name, "property", (StrC)property,
stringize(
static FGameplayAttribute Get<property>Attribute()
{
static FProperty* Prop = FindFieldChecked<FProperty>(<class_name>::StaticClass(), GET_MEMBER_NAME_CHECKED(<class_name>, <property>));
return Prop;
}
)));
body.append( generated_get_attribute );
}
}
void def_attribute_field_value_getters( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
#pragma push_macro(FORCEINLINE)
#undef FORCEINLINE
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE float Get<property>() const
{
return <property>.GetCurrentValue();
}
)));
#pragma pop_macro(FORCEINLINE)
}
}
void def_attribute_field_value_setters( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE void Set<property>(float NewVal)
{
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
if (ensure(AbilityComp))
{
AbilityComp->SetNumericAttributeBase(Get<property>Attribute(), NewVal);
};
}
)));
}
}
void def_attribute_field_initers ( CodeBody body, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append( code_fmt( "property", (StrC)property,
stringize(
FORCEINLINE void Init<property>(float NewVal)
{
<property>.SetBaseValue(NewVal);
<property>.SetCurrentValue(NewVal);
}
)));
}
}
void impl_attribute_fields( CodeBody body, StrC class_name, Array<StringCached> properties )
{
for ( String property : properties )
{
body.append(fmt_newline);
CodeFn field_impl = parse_function( token_fmt( "class_name", class_name, "property", (StrC)property,
stringize(
void <class_name>::Client_OnRep_<property>(FGameplayAttributeData& Prev<property>)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(<class_name>, <property>, Prev<property>)
}
)));
body.append( field_impl );
}
}

View File

@ -0,0 +1,14 @@
# GasaGen
This is a single translation unit meta-program for generating code for the Gasa modules.
It runs before UHT.
GasaGen.cpp is the effective translation unit.
All related code for GasaGen is prefixed with:
```GasaGen_```
Anything with the `gen` namespace (case sensitive, including the files), is related to the Gencpp header. Genccp is used.
Gencpp is a cpp library to make ergonomic use of stage metaprogramming for C++ in C++.
For how GasaGen is built see [`scripts/gen_pass_gasa.ps1`](../../../scripts/gen_pass_gasa.ps1)

View File

@ -1564,7 +1564,12 @@ String CodeConstructor::to_string()
void CodeConstructor::to_string_def( String& result ) void CodeConstructor::to_string_def( String& result )
{ {
AST* ClassStructParent = ast->Parent->Parent; AST* ClassStructParent = ast->Parent->Parent;
result.append( ClassStructParent->Name ); if (ClassStructParent) {
result.append( ClassStructParent->Name );
}
else {
result.append( ast->Name );
}
if ( ast->Params ) if ( ast->Params )
result.append_fmt( "( %S )", ast->Params.to_string() ); result.append_fmt( "( %S )", ast->Params.to_string() );
@ -1588,12 +1593,15 @@ void CodeConstructor::to_string_fwd( String& result )
if ( ast->Params ) if ( ast->Params )
result.append_fmt( "( %S )", ast->Params.to_string() ); result.append_fmt( "( %S )", ast->Params.to_string() );
else else
{ result.append_fmt("()");
if ( ast->InlineCmt )
result.append_fmt( "(); // %S\n", ast->InlineCmt->Content ); if (ast->Body)
else result.append_fmt( " = %S", ast->Body.to_string() );
result.append( "();\n" );
} if ( ast->InlineCmt )
result.append_fmt( "; // %S\n", ast->InlineCmt->Content );
else
result.append( ";" );
} }
String CodeClass::to_string() String CodeClass::to_string()
@ -2263,15 +2271,21 @@ String CodeParam::to_string()
void CodeParam::to_string( String& result ) void CodeParam::to_string( String& result )
{ {
if ( ast->ValueType.ast == nullptr ) if ( ast->Macro )
{ {
result.append_fmt( "%S", ast->Name ); // Were using the convention that if the value type is a macro we ignore everything else
// Related to parsing: ( <macro>, ... )
result.append( ast->Macro.ast->Content );
return; return;
} }
if ( ast->Name ) if ( ast->Name )
result.append_fmt( "%S %S", ast->ValueType.to_string(), ast->Name ); {
if ( ast->ValueType.ast == nullptr )
result.append_fmt( "%S", ast->Name );
else
result.append_fmt( "%S %S", ast->ValueType.to_string(), ast->Name );
}
else else
result.append_fmt( "%S", ast->ValueType.to_string() ); result.append_fmt( "%S", ast->ValueType.to_string() );
@ -5826,11 +5840,12 @@ namespace parser
TF_Preprocess = bit( 2 ), TF_Preprocess = bit( 2 ),
TF_Preprocess_Cond = bit( 3 ), TF_Preprocess_Cond = bit( 3 ),
TF_Attribute = bit( 6 ), TF_Attribute = bit( 6 ),
TF_AccessSpecifier = bit( 7 ), TF_AccessOperator = bit( 7 ),
TF_Specifier = bit( 8 ), TF_AccessSpecifier = bit( 8 ),
TF_EndDefinition = bit( 9 ), // Either ; or } TF_Specifier = bit( 9 ),
TF_Formatting = bit( 10 ), TF_EndDefinition = bit( 10 ), // Either ; or }
TF_Literal = bit( 11 ), TF_Formatting = bit( 11 ),
TF_Literal = bit( 12 ),
TF_Null = 0, TF_Null = 0,
}; };
@ -5854,6 +5869,11 @@ namespace parser
return { Length, Text }; return { Length, Text };
} }
bool is_access_operator()
{
return bitfield_is_equal( u32, Flags, TF_AccessOperator );
}
bool is_access_specifier() bool is_access_specifier()
{ {
return bitfield_is_equal( u32, Flags, TF_AccessSpecifier ); return bitfield_is_equal( u32, Flags, TF_AccessSpecifier );
@ -6258,6 +6278,11 @@ namespace parser
TokType type = ETokType::to_type( token ); TokType type = ETokType::to_type( token );
if (type <= TokType::Access_Public && type >= TokType::Access_Private )
{
token.Flags |= TF_AccessSpecifier;
}
if ( type == ETokType::Decl_Extern_Linkage ) if ( type == ETokType::Decl_Extern_Linkage )
{ {
SkipWhitespace(); SkipWhitespace();
@ -6370,7 +6395,7 @@ namespace parser
scanner++; scanner++;
length++; length++;
} }
if ( scanner[ 1 ] == '(' ) if ( scanner[ 0 ] == '(' )
{ {
length++; length++;
} }
@ -6439,7 +6464,7 @@ namespace parser
token.Text = scanner; token.Text = scanner;
token.Length = 1; token.Length = 1;
token.Type = TokType::Access_MemberSymbol; token.Type = TokType::Access_MemberSymbol;
token.Flags = TF_AccessSpecifier; token.Flags = TF_AccessOperator;
if ( left ) if ( left )
{ {
@ -6805,7 +6830,7 @@ namespace parser
{ {
token.Length++; token.Length++;
// token.Type = TokType::Access_PointerToMemberSymbol; // token.Type = TokType::Access_PointerToMemberSymbol;
token.Flags |= TF_AccessSpecifier; token.Flags |= TF_AccessOperator;
move_forward(); move_forward();
if ( current == '*' ) if ( current == '*' )
@ -7199,6 +7224,7 @@ namespace parser
internal CodeBody parse_class_struct_body( TokType which, Token name = NullToken ); internal CodeBody parse_class_struct_body( TokType which, Token name = NullToken );
internal Code parse_class_struct( TokType which, bool inplace_def ); internal Code parse_class_struct( TokType which, bool inplace_def );
internal CodeDefine parse_define(); internal CodeDefine parse_define();
internal Code parse_expression();
internal Code parse_forward_or_definition( TokType which, bool is_inplace ); internal Code parse_forward_or_definition( TokType which, bool is_inplace );
internal CodeFn parse_function_after_name( ModuleFlag mflags, CodeAttributes attributes, CodeSpecifiers specifiers, CodeType ret_type, Token name ); internal CodeFn parse_function_after_name( ModuleFlag mflags, CodeAttributes attributes, CodeSpecifiers specifiers, CodeType ret_type, Token name );
internal Code parse_function_body(); internal Code parse_function_body();
@ -7217,7 +7243,7 @@ namespace parser
internal CodeVar parse_variable_declaration_list(); internal CodeVar parse_variable_declaration_list();
internal CodeClass parse_class( bool inplace_def = false ); internal CodeClass parse_class( bool inplace_def = false );
internal CodeConstructor parse_constructor(); internal CodeConstructor parse_constructor( CodeSpecifiers specifiers );
internal CodeDestructor parse_destructor( CodeSpecifiers specifiers = NoCode ); internal CodeDestructor parse_destructor( CodeSpecifiers specifiers = NoCode );
internal CodeEnum parse_enum( bool inplace_def = false ); internal CodeEnum parse_enum( bool inplace_def = false );
internal CodeBody parse_export_body(); internal CodeBody parse_export_body();
@ -7707,6 +7733,7 @@ namespace parser
{ {
access = currtok.to_access_specifier(); access = currtok.to_access_specifier();
// <ModuleFlags> <class/struct> <Attributes> <Name> : <Access Specifier> // <ModuleFlags> <class/struct> <Attributes> <Name> : <Access Specifier>
eat( currtok.Type );
} }
Token parent_tok = parse_identifier(); Token parent_tok = parse_identifier();
@ -7791,6 +7818,13 @@ namespace parser
switch ( currtok_noskip.Type ) switch ( currtok_noskip.Type )
{ {
case TokType::Statement_End:
{
// TODO(Ed): Convert this to a general warning procedure
log_fmt("Dangling end statement found %S\n", currtok_noskip.to_string());
eat( TokType::Statement_End );
continue;
}
case TokType::NewLine : case TokType::NewLine :
member = fmt_newline; member = fmt_newline;
eat( TokType::NewLine ); eat( TokType::NewLine );
@ -7941,12 +7975,14 @@ namespace parser
case TokType::Spec_Consteval : case TokType::Spec_Consteval :
case TokType::Spec_Constexpr : case TokType::Spec_Constexpr :
case TokType::Spec_Constinit : case TokType::Spec_Constinit :
case TokType::Spec_Explicit:
case TokType::Spec_ForceInline : case TokType::Spec_ForceInline :
case TokType::Spec_Inline : case TokType::Spec_Inline :
case TokType::Spec_Mutable : case TokType::Spec_Mutable :
case TokType::Spec_NeverInline : case TokType::Spec_NeverInline :
case TokType::Spec_Static : case TokType::Spec_Static :
case TokType::Spec_Volatile : case TokType::Spec_Volatile :
case TokType::Spec_Virtual:
{ {
SpecifierT specs_found[ 16 ] { ESpecifier::NumSpecifiers }; SpecifierT specs_found[ 16 ] { ESpecifier::NumSpecifiers };
s32 NumSpecifiers = 0; s32 NumSpecifiers = 0;
@ -7955,28 +7991,40 @@ namespace parser
{ {
SpecifierT spec = ESpecifier::to_type( currtok ); SpecifierT spec = ESpecifier::to_type( currtok );
b32 ignore_spec = false;
switch ( spec ) switch ( spec )
{ {
case ESpecifier::Constexpr : case ESpecifier::Constexpr :
case ESpecifier::Constinit : case ESpecifier::Constinit :
case ESpecifier::Explicit:
case ESpecifier::Inline : case ESpecifier::Inline :
case ESpecifier::ForceInline : case ESpecifier::ForceInline :
case ESpecifier::Mutable : case ESpecifier::Mutable :
case ESpecifier::NeverInline : case ESpecifier::NeverInline :
case ESpecifier::Static : case ESpecifier::Static :
case ESpecifier::Volatile : case ESpecifier::Volatile :
case ESpecifier::Virtual:
break; break;
case ESpecifier::Consteval : case ESpecifier::Consteval :
expects_function = true; expects_function = true;
break; break;
case ESpecifier::Const :
ignore_spec = true;
break;
default : default :
log_failure( "Invalid specifier %s for variable\n%s", ESpecifier::to_str( spec ), Context.to_string() ); log_failure( "Invalid specifier %s for variable\n%s", ESpecifier::to_str( spec ), Context.to_string() );
Context.pop(); Context.pop();
return CodeInvalid; return CodeInvalid;
} }
// Every specifier after would be considered part of the type type signature
if (ignore_spec)
break;
specs_found[ NumSpecifiers ] = spec; specs_found[ NumSpecifiers ] = spec;
NumSpecifiers++; NumSpecifiers++;
eat( currtok.Type ); eat( currtok.Type );
@ -8018,7 +8066,7 @@ namespace parser
{ {
if ( str_compare( name.Text, currtok.Text, name.Length ) == 0 ) if ( str_compare( name.Text, currtok.Text, name.Length ) == 0 )
{ {
member = parse_constructor(); member = parse_constructor( specifiers );
// <Attributes> <Specifiers> <Name>() // <Attributes> <Specifiers> <Name>()
break; break;
} }
@ -8109,12 +8157,38 @@ namespace parser
} }
Token tok = tokens[ idx - 1 ]; Token tok = tokens[ idx - 1 ];
if ( tok.is_specifier() && is_trailing( ESpecifier::to_type(tok)) )
{
// <which> <type_identifier>(...) <specifier> ...;
s32 spec_idx = idx - 1;
Token spec = tokens[spec_idx];
while ( spec.is_specifier() && is_trailing( ESpecifier::to_type(spec)) )
{
-- spec_idx;
spec = tokens[spec_idx];
}
if ( tokens[spec_idx].Type == TokType::Capture_End )
{
// Forward declaration with trailing specifiers for a procedure
tok = tokens[spec_idx];
Code result = parse_operator_function_or_variable( false, { nullptr }, { nullptr } );
// <Attributes> <Specifiers> <ReturnType/ValueType> <operator <Op>, or Name> ...
Context.pop();
return result;
}
log_failure( "Unsupported or bad member definition after %s declaration\n%s", to_str(which), Context.to_string() );
Context.pop();
return CodeInvalid;
}
if ( tok.Type == TokType::Identifier ) if ( tok.Type == TokType::Identifier )
{ {
tok = tokens[ idx - 2 ]; tok = tokens[ idx - 2 ];
bool is_indirection = tok.Type == TokType::Ampersand || tok.Type == TokType::Star; bool is_indirection = tok.Type == TokType::Ampersand || tok.Type == TokType::Star;
bool ok_to_parse = false; bool ok_to_parse = false;
if ( tok.Type == TokType::BraceCurly_Close ) if ( tok.Type == TokType::BraceCurly_Close )
@ -8130,6 +8204,17 @@ namespace parser
// <which> <type_identifier> <identifier>; // <which> <type_identifier> <identifier>;
ok_to_parse = true; ok_to_parse = true;
} }
else if ( tok.Type == TokType::Assign_Classifer
&& tokens[idx - 4].Type == TokType::Decl_Class
&& tokens[idx - 5].Type == which )
{
// Its a forward declaration of an enum class
// <enum> <class> <type_identifier> : <identifier>;
ok_to_parse = true;
Code result = parse_enum();
Context.pop();
return result;
}
else if ( is_indirection ) else if ( is_indirection )
{ {
// Its a indirection type with type ID using struct namespace. // Its a indirection type with type ID using struct namespace.
@ -8139,7 +8224,7 @@ namespace parser
if ( ! ok_to_parse ) if ( ! ok_to_parse )
{ {
log_failure( "Unsupported or bad member definition after struct declaration\n%s", Context.to_string() ); log_failure( "Unsupported or bad member definition after %s declaration\n%s", to_str(which), Context.to_string() );
Context.pop(); Context.pop();
return CodeInvalid; return CodeInvalid;
} }
@ -8167,7 +8252,7 @@ namespace parser
} }
else else
{ {
log_failure( "Unsupported or bad member definition after struct declaration\n%s", Context.to_string() ); log_failure( "Unsupported or bad member definition after %s declaration\n%S", to_str(which).Ptr, Context.to_string() );
Context.pop(); Context.pop();
return CodeInvalid; return CodeInvalid;
} }
@ -8219,6 +8304,40 @@ namespace parser
return define; return define;
} }
internal inline
Code parse_assignment_expression()
{
Code expr = { nullptr };
eat( TokType::Operator );
// <Attributes> <Specifiers> <ValueType> <Name> =
Token expr_tok = currtok;
if ( currtok.Type == TokType::Statement_End && currtok.Type != TokType::Comma )
{
log_failure( "Expected expression after assignment operator\n%s", Context.to_string() );
Context.pop();
return CodeInvalid;
}
s32 level = 0;
while ( left && currtok.Type != TokType::Statement_End && (currtok.Type != TokType::Comma || level > 0) )
{
if (currtok.Type == TokType::Capture_Start)
level++;
else if (currtok.Type == TokType::Capture_End)
level--;
eat( currtok.Type );
}
expr_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )expr_tok.Text - 1;
expr = untyped_str( expr_tok );
// = <Expression>
return expr;
}
internal inline Code parse_forward_or_definition( TokType which, bool is_inplace ) internal inline Code parse_forward_or_definition( TokType which, bool is_inplace )
{ {
Code result = CodeInvalid; Code result = CodeInvalid;
@ -8416,6 +8535,13 @@ namespace parser
switch ( currtok_noskip.Type ) switch ( currtok_noskip.Type )
{ {
case TokType::Statement_End:
{
// TODO(Ed): Convert this to a general warning procedure
log_fmt("Dangling end statement found %S\n", currtok_noskip.to_string());
eat( TokType::Statement_End );
continue;
}
case TokType::NewLine : case TokType::NewLine :
// Empty lines are auto skipped by Tokens.current() // Empty lines are auto skipped by Tokens.current()
member = fmt_newline; member = fmt_newline;
@ -8625,7 +8751,7 @@ namespace parser
case TokType::Type_double : case TokType::Type_double :
case TokType::Type_int : case TokType::Type_int :
{ {
bool found_operator_cast = false; bool found_operator_cast_outside_class_implmentation = false;
s32 idx = Context.Tokens.Idx; s32 idx = Context.Tokens.Idx;
for ( ; idx < Context.Tokens.Arr.num(); idx++ ) for ( ; idx < Context.Tokens.Arr.num(); idx++ )
@ -8643,12 +8769,12 @@ namespace parser
} }
if ( tok.Type == TokType::Decl_Operator ) if ( tok.Type == TokType::Decl_Operator )
found_operator_cast = true; found_operator_cast_outside_class_implmentation = true;
break; break;
} }
if ( found_operator_cast ) if ( found_operator_cast_outside_class_implmentation )
{ {
member = parse_operator_cast(); member = parse_operator_cast();
// <Attributes> <Specifiers> <Name>::operator <Type>() { ... } // <Attributes> <Specifiers> <Name>::operator <Type>() { ... }
@ -9197,8 +9323,10 @@ namespace parser
return { nullptr }; return { nullptr };
} }
Code macro = { nullptr };
CodeType type = { nullptr }; CodeType type = { nullptr };
Code value = { nullptr }; Code value = { nullptr };
Token name = NullToken;
if ( check( TokType::Varadic_Argument ) ) if ( check( TokType::Varadic_Argument ) )
{ {
@ -9211,50 +9339,61 @@ namespace parser
// or < ... > // or < ... >
} }
type = parse_type(); // Ex: Unreal has this type of macro: vvvvvvvv
if ( type == Code::Invalid ) // COREUOBJECT_API void CallFunction( FFrame& Stack, RESULT_DECL, UFunction* Function );
// This is so that a 'ValueType for the param is a preprocesor macro to support this nasty macro usage'
if ( ! check(TokType::Preprocess_Macro))
{ {
Context.pop(); type = parse_type();
return CodeInvalid; if ( type == Code::Invalid )
}
// ( <ValueType>
Token name = NullToken;
if ( check( TokType::Identifier ) )
{
name = currtok;
eat( TokType::Identifier );
// ( <ValueType> <Name>
if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) )
{ {
eat( TokType::Operator ); Context.pop();
// ( <ValueType> <Name> = return CodeInvalid;
Token value_tok = currtok;
if ( currtok.Type == TokType::Comma )
{
log_failure( "Expected value after assignment operator\n%s.", Context.to_string() );
Context.pop();
return CodeInvalid;
}
while ( left && currtok.Type != TokType::Comma && currtok.Type != TokType::Capture_End )
{
value_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )value_tok.Text;
eat( currtok.Type );
}
value = untyped_str( strip_formatting( value_tok, strip_formatting_dont_preserve_newlines ) );
// ( <ValueType> <Name> = <Expression>
} }
// ( <ValueType>
if ( check( TokType::Identifier ) )
{
name = currtok;
eat( TokType::Identifier );
// ( <ValueType> <Name>
if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) )
{
eat( TokType::Operator );
// ( <ValueType> <Name> =
Token value_tok = currtok;
if ( currtok.Type == TokType::Comma )
{
log_failure( "Expected value after assignment operator\n%s.", Context.to_string() );
Context.pop();
return CodeInvalid;
}
while ( left && currtok.Type != TokType::Comma && currtok.Type != TokType::Capture_End )
{
value_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )value_tok.Text;
eat( currtok.Type );
}
value = untyped_str( strip_formatting( value_tok, strip_formatting_dont_preserve_newlines ) );
// ( <ValueType> <Name> = <Expression>
}
}
}
else {
macro = parse_simple_preprocess(ETokType::Preprocess_Macro);
// ( <Macro>
} }
CodeParam result = ( CodeParam )make_code(); CodeParam result = ( CodeParam )make_code();
result->Type = Parameters; result->Type = Parameters;
result->Macro = macro;
if ( name.Length > 0 ) if ( name.Length > 0 )
result->Name = get_cached_string( name ); result->Name = get_cached_string( name );
@ -9281,51 +9420,60 @@ namespace parser
// ( <ValueType> <Name> = <Expression>, ... // ( <ValueType> <Name> = <Expression>, ...
} }
type = parse_type(); if ( ! check(TokType::Preprocess_Macro))
if ( type == Code::Invalid )
{ {
Context.pop(); type = parse_type();
return CodeInvalid; if ( type == Code::Invalid )
}
// ( <ValueType> <Name> = <Expression>, <ValueType>
name = { nullptr, 0, TokType::Invalid, false };
if ( check( TokType::Identifier ) )
{
name = currtok;
eat( TokType::Identifier );
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name>
if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) )
{ {
eat( TokType::Operator ); Context.pop();
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> = return CodeInvalid;
Token value_tok = currtok;
if ( currtok.Type == TokType::Comma )
{
log_failure( "Expected value after assignment operator\n%s", Context.to_string() );
Context.pop();
return CodeInvalid;
}
while ( left && currtok.Type != TokType::Comma && currtok.Type != TokType::Capture_End )
{
value_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )value_tok.Text;
eat( currtok.Type );
}
value = untyped_str( strip_formatting( value_tok, strip_formatting_dont_preserve_newlines ) );
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> = <Expression>
} }
// ( <ValueType> <Name> = <Expression>, <ValueType>
name = { nullptr, 0, TokType::Invalid, false };
if ( check( TokType::Identifier ) )
{
name = currtok;
eat( TokType::Identifier );
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name>
if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) )
{
eat( TokType::Operator );
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> =
Token value_tok = currtok;
if ( currtok.Type == TokType::Comma )
{
log_failure( "Expected value after assignment operator\n%s", Context.to_string() );
Context.pop();
return CodeInvalid;
}
while ( left && currtok.Type != TokType::Comma && currtok.Type != TokType::Capture_End )
{
value_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )value_tok.Text;
eat( currtok.Type );
}
value = untyped_str( strip_formatting( value_tok, strip_formatting_dont_preserve_newlines ) );
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> = <Expression>
}
}
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> = <Expression>, ..
}
else {
macro = parse_simple_preprocess(ETokType::Preprocess_Macro);
// ( ..., <Macro>
} }
// ( <ValueType> <Name> = <Expression>, <ValueType> <Name> = <Expression>, ..
CodeParam param = ( CodeParam )make_code(); CodeParam param = ( CodeParam )make_code();
param->Type = Parameters; param->Type = Parameters;
param->Macro = macro;
if ( name.Length > 0 ) if ( name.Length > 0 )
param->Name = get_cached_string( name ); param->Name = get_cached_string( name );
@ -9552,26 +9700,8 @@ namespace parser
if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) ) if ( bitfield_is_equal( u32, currtok.Flags, TF_Assign ) )
{ {
eat( TokType::Operator );
// <Attributes> <Specifiers> <ValueType> <Name> =
Token expr_tok = currtok;
if ( currtok.Type == TokType::Statement_End || currtok.Type != TokType::Comma )
{
log_failure( "Expected expression after assignment operator\n%s", Context.to_string() );
Context.pop();
return CodeInvalid;
}
while ( left && currtok.Type != TokType::Statement_End || currtok.Type != TokType::Comma )
{
eat( currtok.Type );
}
expr_tok.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )expr_tok.Text - 1;
expr = untyped_str( expr_tok );
// <Attributes> <Specifiers> <ValueType> <Name> = <Expression> // <Attributes> <Specifiers> <ValueType> <Name> = <Expression>
expr = parse_assignment_expression();
} }
if ( currtok.Type == TokType::BraceCurly_Open ) if ( currtok.Type == TokType::BraceCurly_Open )
@ -9789,7 +9919,7 @@ namespace parser
return result; return result;
} }
internal CodeConstructor parse_constructor() internal CodeConstructor parse_constructor( CodeSpecifiers specifiers )
{ {
push_scope(); push_scope();
@ -9808,14 +9938,14 @@ namespace parser
eat( TokType::Assign_Classifer ); eat( TokType::Assign_Classifer );
// <Name> ( <Parameters> ) : // <Name> ( <Parameters> ) :
Token initializer_list_tok = NullToken; Token initializer_list_tok = currtok;
s32 level = 0; s32 level = 0;
while ( left && ( currtok.Type != TokType::BraceCurly_Open || level > 0 ) ) while ( left && ( currtok.Type != TokType::BraceCurly_Open || level > 0 ) )
{ {
if ( currtok.Type == TokType::BraceCurly_Open ) if (currtok.Type == TokType::Capture_Start)
level++; level++;
else if ( currtok.Type == TokType::BraceCurly_Close ) else if ( currtok.Type == TokType::Capture_End )
level--; level--;
eat( currtok.Type ); eat( currtok.Type );
@ -9833,6 +9963,10 @@ namespace parser
body = parse_function_body(); body = parse_function_body();
// <Name> ( <Parameters> ) { <Body> } // <Name> ( <Parameters> ) { <Body> }
} }
else if ( check( TokType::Operator) && currtok.Text[ 0 ] == '=' )
{
body = parse_assignment_expression();
}
else else
{ {
Token stmt_end = currtok; Token stmt_end = currtok;
@ -9844,15 +9978,21 @@ namespace parser
// <Name> ( <Parameters> ); <InlineCmt> // <Name> ( <Parameters> ); <InlineCmt>
} }
// TODO(Ed): Constructors can have post-fix specifiers
CodeConstructor result = ( CodeConstructor )make_code(); CodeConstructor result = ( CodeConstructor )make_code();
result->Name = get_cached_string(identifier);
result->Specs = specifiers;
if ( params ) if ( params )
result->Params = params; result->Params = params;
if ( initializer_list ) if ( initializer_list )
result->InitializerList = initializer_list; result->InitializerList = initializer_list;
if ( body ) if ( body && body->Type == ECode::Function_Body )
{ {
result->Body = body; result->Body = body;
result->Type = ECode::Constructor; result->Type = ECode::Constructor;
@ -10660,54 +10800,58 @@ namespace parser
s32 NumSpecifiers = 0; s32 NumSpecifiers = 0;
attributes = parse_attributes(); attributes = parse_attributes();
// <export> template< <Parameters> > <Attributes> // <export> template< <Parameters> > <Attributes> <specifiers.
while ( left && currtok.is_specifier() ) // Specifiers Parsing
{ {
SpecifierT spec = ESpecifier::to_type( currtok ); while ( left && currtok.is_specifier() )
switch ( spec )
{ {
case ESpecifier::Const : SpecifierT spec = ESpecifier::to_type( currtok );
case ESpecifier::Constexpr :
case ESpecifier::Constinit : switch ( spec )
case ESpecifier::External_Linkage : {
case ESpecifier::Global : case ESpecifier::Const :
case ESpecifier::Inline : case ESpecifier::Constexpr :
case ESpecifier::ForceInline : case ESpecifier::Constinit :
case ESpecifier::Local_Persist : case ESpecifier::External_Linkage :
case ESpecifier::Mutable : case ESpecifier::Global :
case ESpecifier::Static : case ESpecifier::Inline :
case ESpecifier::Thread_Local : case ESpecifier::ForceInline :
case ESpecifier::Volatile : case ESpecifier::Local_Persist :
case ESpecifier::Mutable :
case ESpecifier::Static :
case ESpecifier::Thread_Local :
case ESpecifier::Volatile :
break;
case ESpecifier::Consteval :
expects_function = true;
break;
default :
log_failure( "Invalid specifier %s for variable or function\n%s", ESpecifier::to_str( spec ), Context.to_string() );
Context.pop();
return CodeInvalid;
}
// Ignore const it will be handled by the type
if ( spec == ESpecifier::Const )
break; break;
case ESpecifier::Consteval : specs_found[ NumSpecifiers ] = spec;
expects_function = true; NumSpecifiers++;
break; eat( currtok.Type );
default :
log_failure( "Invalid specifier %s for variable or function\n%s", ESpecifier::to_str( spec ), Context.to_string() );
Context.pop();
return CodeInvalid;
} }
// Ignore const it will be handled by the type if ( NumSpecifiers )
if ( spec == ESpecifier::Const ) {
continue; specifiers = def_specifiers( NumSpecifiers, specs_found );
}
specs_found[ NumSpecifiers ] = spec; // <export> template< <Parameters> > <Attributes> <Specifiers>
NumSpecifiers++;
eat( currtok.Type );
} }
if ( NumSpecifiers ) // TODO(Ed) : Port over user-defined operator cast detection from
{ //parse_global_nspace (which is a source defintion version) or parse_class_struct_body (which is an inline class definition)
specifiers = def_specifiers( NumSpecifiers, specs_found );
}
// <export> template< <Parameters> > <Attributes> <Specifiers>
// TODO(Ed) : Port over operator cast detection from parse_global_nspace or parse_class_struct_body
definition = parse_operator_function_or_variable( expects_function, attributes, specifiers ); definition = parse_operator_function_or_variable( expects_function, attributes, specifiers );
// <export> template< <Parameters> > <Attributes> <Specifiers> ... // <export> template< <Parameters> > <Attributes> <Specifiers> ...
@ -10781,12 +10925,13 @@ namespace parser
if ( currtok.Type == TokType::Decl_Class || currtok.Type == TokType::Decl_Enum || currtok.Type == TokType::Decl_Struct if ( currtok.Type == TokType::Decl_Class || currtok.Type == TokType::Decl_Enum || currtok.Type == TokType::Decl_Struct
|| currtok.Type == TokType::Decl_Union ) || currtok.Type == TokType::Decl_Union )
{ {
name = currtok;
eat( currtok.Type ); eat( currtok.Type );
// <Attributes> <Specifiers> <class, enum, struct, union> // <Attributes> <Specifiers> <class, enum, struct, union>
name.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )name.Text; name = parse_identifier();
eat( TokType::Identifier );
// name.Length = ( ( sptr )currtok.Text + currtok.Length ) - ( sptr )name.Text;
// eat( TokType::Identifier );
Context.Scope->Name = name; Context.Scope->Name = name;
// <Attributes> <Specifiers> <class, enum, struct, union> <Name> // <Attributes> <Specifiers> <class, enum, struct, union> <Name>
} }
@ -10835,6 +10980,12 @@ namespace parser
name.Length = ( ( sptr )prevtok.Text + prevtok.Length ) - ( sptr )name.Text; name.Length = ( ( sptr )prevtok.Text + prevtok.Length ) - ( sptr )name.Text;
// <Attributes> <Specifiers> <Compound type expression> // <Attributes> <Specifiers> <Compound type expression>
} }
else if ( currtok.Type == TokType::Type_Typename )
{
name = currtok;
eat(TokType::Type_Typename);
// <typename>
}
// The usual Identifier type signature that may have namespace qualifiers // The usual Identifier type signature that may have namespace qualifiers
else else
@ -11685,8 +11836,54 @@ CodeConstructor parse_constructor( StrC def )
if ( toks.Arr == nullptr ) if ( toks.Arr == nullptr )
return CodeInvalid; return CodeInvalid;
// TODO(Ed): Constructors can have prefix attributes
CodeSpecifiers specifiers;
SpecifierT specs_found[ 16 ] { ESpecifier::NumSpecifiers };
s32 NumSpecifiers = 0;
while ( left && currtok.is_specifier() )
{
SpecifierT spec = ESpecifier::to_type( currtok );
b32 ignore_spec = false;
switch ( spec )
{
case ESpecifier::Constexpr :
case ESpecifier::Explicit:
case ESpecifier::Inline :
case ESpecifier::ForceInline :
case ESpecifier::NeverInline :
break;
case ESpecifier::Const :
ignore_spec = true;
break;
default :
log_failure( "Invalid specifier %s for variable\n%s", ESpecifier::to_str( spec ), Context.to_string() );
Context.pop();
return CodeInvalid;
}
// Every specifier after would be considered part of the type type signature
if (ignore_spec)
break;
specs_found[ NumSpecifiers ] = spec;
NumSpecifiers++;
eat( currtok.Type );
}
if ( NumSpecifiers )
{
specifiers = def_specifiers( NumSpecifiers, specs_found );
// <specifiers> ...
}
Context.Tokens = toks; Context.Tokens = toks;
CodeConstructor result = parse_constructor(); CodeConstructor result = parse_constructor( specifiers );
return result; return result;
} }
@ -11699,6 +11896,9 @@ CodeDestructor parse_destructor( StrC def )
if ( toks.Arr == nullptr ) if ( toks.Arr == nullptr )
return CodeInvalid; return CodeInvalid;
// TODO(Ed): Destructors can have prefix attributes
// TODO(Ed): Destructors can have virtual
Context.Tokens = toks; Context.Tokens = toks;
CodeDestructor result = parse_destructor(); CodeDestructor result = parse_destructor();
return result; return result;

View File

@ -70,9 +70,9 @@ using LogFailType = sw ( * )( char const*, ... );
enum class AccessSpec : u32 enum class AccessSpec : u32
{ {
Default, Default,
Public,
Protected,
Private, Private,
Protected,
Public,
Num_AccessSpec, Num_AccessSpec,
Invalid, Invalid,
@ -82,9 +82,9 @@ inline char const* to_str( AccessSpec type )
{ {
local_persist char const* lookup[ ( u32 )AccessSpec::Num_AccessSpec ] = { local_persist char const* lookup[ ( u32 )AccessSpec::Num_AccessSpec ] = {
"", "",
"public",
"protected",
"private", "private",
"protected",
"public",
}; };
if ( type > AccessSpec::Public ) if ( type > AccessSpec::Public )
@ -814,6 +814,7 @@ struct AST
union union
{ {
AST* Macro; // Parameter
AST* BitfieldSize; // Variable (Class/Struct Data Member) AST* BitfieldSize; // Variable (Class/Struct Data Member)
AST* Params; // Constructor, Function, Operator, Template, Typename AST* Params; // Constructor, Function, Operator, Template, Typename
}; };
@ -2014,7 +2015,7 @@ struct AST_Constructor
Code Next; Code Next;
parser::Token* Tok; parser::Token* Tok;
Code Parent; Code Parent;
char _PAD_NAME_[ sizeof( StringCached ) ]; StringCached Name;
CodeT Type; CodeT Type;
char _PAD_UNUSED_[ sizeof( ModuleFlag ) + sizeof( u32 ) ]; char _PAD_UNUSED_[ sizeof( ModuleFlag ) + sizeof( u32 ) ];
}; };
@ -2616,11 +2617,11 @@ struct AST_Param
struct struct
{ {
char _PAD_PROPERTIES_2_[ sizeof( AST* ) * 3 ]; char _PAD_PROPERTIES_1_[ sizeof( AST* ) * 3 ];
CodeType ValueType; CodeType ValueType;
char _PAD_PROPERTIES_[ sizeof( AST* ) ]; Code Macro;
Code Value; Code Value;
char _PAD_PROPERTIES_3_[ sizeof( AST* ) ]; char _PAD_PROPERTIES_2_[ sizeof( AST* ) ];
}; };
}; };

View File

@ -10,6 +10,7 @@ Implementation design perfs:
* Lift to more specific or generalized code-pathsonly when necessary * Lift to more specific or generalized code-pathsonly when necessary
* Minimize distinct code-paths * Minimize distinct code-paths
* Use classes as "filters", keep things "mega-structed" * Use classes as "filters", keep things "mega-structed"
* Use composition for large sets of entiites (when possible).
* Never pre-emtively make interfaces or interface-like patterns * Never pre-emtively make interfaces or interface-like patterns
* Keep everything data-wise in the runtime unless there is a measurable performance cost. * Keep everything data-wise in the runtime unless there is a measurable performance cost.
* Some exploratory optimizations for educational purposes. * Some exploratory optimizations for educational purposes.

View File

@ -105,7 +105,7 @@ function run-gengasa
$path_AbilitySystem = join-path $path_gasa 'AbilitySystem' $path_AbilitySystem = join-path $path_gasa 'AbilitySystem'
$include = @( $include = @(
'GasaAttributeSet.h', 'GasaAttributeSet.cpp' 'GasaAttributeSet.h', 'GasaAttributeSet.cpp', 'LETS_SEE.h'
) )
format-cpp $path_AbilitySystem $include $null format-cpp $path_AbilitySystem $include $null
} }