From c4926131796787b3b4fb53173f2586d4346c99f5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 21 Oct 2024 22:39:40 -0400 Subject: [PATCH] Setting up GasaGen in the editor module issues with clang format... --- Project/Source/Gasa/GasaCommon.h | 2 + Project/Source/GasaEditor/GasaEditor.Build.cs | 17 +- .../Source/GasaEditor/GasaEditorModule.cpp | 58 +- Project/Source/GasaEditor/GasaEditorModule.h | 2 +- Project/Source/GasaEditor/GasaGen/GasaGen.cpp | 114 + Project/Source/GasaEditor/GasaGen/GasaGen.h | 3 + .../GasaGen/GasaGen_AttributeSets.cpp | 8 + .../GasaGen/GasaGen_AttributeSets.h | 3 + .../GasaEditor/GasaGen/GasaGen_Common.h | 131 + .../GasaGen/GasaGen_DevOptionsCache.cpp | 169 + .../GasaGen/GasaGen_DevOptionsCache.h | 3 + .../GasaEditor/GasaGen/gencpp/gen.builder.cpp | 88 + .../GasaEditor/GasaGen/gencpp/gen.builder.hpp | 50 + .../Source/GasaEditor/GasaGen/gencpp/gen.cpp | 12865 ++++++++++++++++ .../GasaEditor/GasaGen/gencpp/gen.dep.cpp | 2479 +++ .../GasaEditor/GasaGen/gencpp/gen.dep.hpp | 2886 ++++ .../Source/GasaEditor/GasaGen/gencpp/gen.hpp | 6923 +++++++++ .../GasaEditor/GasaGen/gencpp/gen.scanner.cpp | 1135 ++ .../GasaEditor/GasaGen/gencpp/gen.scanner.hpp | 619 + Project/Source/GasaGen/GasaGenCommon.cpp | 2 +- .../GasaGen/GasaGen_DevOptionsCache.cpp | 33 +- .../GasaGen/GasaGen_UGasaAttributeSet.cpp | 4 - Project/Source/GasaGen/gen.scanner.cpp | 1 - 23 files changed, 27581 insertions(+), 14 deletions(-) create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen.h create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.h create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen_Common.h create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.h create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.hpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.dep.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.dep.hpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.hpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.scanner.cpp create mode 100644 Project/Source/GasaEditor/GasaGen/gencpp/gen.scanner.hpp diff --git a/Project/Source/Gasa/GasaCommon.h b/Project/Source/Gasa/GasaCommon.h index b2354b5..3fe2400 100644 --- a/Project/Source/Gasa/GasaCommon.h +++ b/Project/Source/Gasa/GasaCommon.h @@ -8,10 +8,12 @@ #define internal static #define local_persist static +#ifndef ccast #define ccast( Type, Value ) ( *const_cast<(Type)*>( &( Value ) ) ) #define pcast( Type, Value ) ( *reinterpret_cast<(Type)*>( &( Value ) ) ) #define rcast( Type, Value ) reinterpret_cast( Value ) #define scast( Type, Value ) static_cast( Value ) +#endif #define bit(position) (1 << position) diff --git a/Project/Source/GasaEditor/GasaEditor.Build.cs b/Project/Source/GasaEditor/GasaEditor.Build.cs index 2e45960..3961d50 100644 --- a/Project/Source/GasaEditor/GasaEditor.Build.cs +++ b/Project/Source/GasaEditor/GasaEditor.Build.cs @@ -1,4 +1,4 @@ -using ModuleRules = UnrealBuildTool.ModuleRules; +using ModuleRules = UnrealBuildTool.ModuleRules; using ReadOnlyTargetRules = UnrealBuildTool.ReadOnlyTargetRules; public class GasaEditor : ModuleRules @@ -15,6 +15,7 @@ public class GasaEditor : ModuleRules "Core", "Engine", "CoreUObject", + "EditorStyle", "PropertyEditor", "SlateCore", "Slate", @@ -25,5 +26,17 @@ public class GasaEditor : ModuleRules PublicIncludePaths.Add("GasaEditor"); PrivateDependencyModuleNames.Add("Gasa"); - } + + bWarningsAsErrors = false; + ShadowVariableWarningLevel = UnrealBuildTool.WarningLevel.Warning; + UndefinedIdentifierWarningLevel = UnrealBuildTool.WarningLevel.Warning; + + // gencpp related defines + PublicDefinitions.Add("Build_Debug=1"); + PublicDefinitions.Add("GEN_TIME=1"); + PublicDefinitions.Add("GEN_EXECUTION_EXPRESSION_SUPPORT=0"); + PublicDefinitions.Add("GEN_EXPOSE_BACKEND=1"); + PublicDefinitions.Add("GEN_PARSER_DISABLE_MACRO_TYPEDEF=0"); + } } + diff --git a/Project/Source/GasaEditor/GasaEditorModule.cpp b/Project/Source/GasaEditor/GasaEditorModule.cpp index dd2a068..10bd3c1 100644 --- a/Project/Source/GasaEditor/GasaEditorModule.cpp +++ b/Project/Source/GasaEditor/GasaEditorModule.cpp @@ -1,12 +1,66 @@ #include "GasaEditorModule.h" + +#include "Framework/Commands/Commands.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Commands/UICommandList.h" +#include "EditorStyleSet.h" +#include "LevelEditor.h" + #include "EditorDetails/GlobeProgressBarDetails.h" +#include "GasaGen/GasaGen.h" #include "UI/GlobeProgressBar.h" + IMPLEMENT_PRIMARY_GAME_MODULE(FGasaEditorModule, GasaEditor, GasaEditor); +#define LOCTEXT_NAMESPACE "GasaEditor" + +class FGasaEditorCommands : public TCommands +{ +public: + FGasaEditorCommands() + : TCommands( + TEXT("GasaEditorCommands"), + NSLOCTEXT("Contexts", "GasaEditorCommands", "GASATHON Editor Commands"), + NAME_None, + FAppStyle::GetAppStyleSetName()) + {} + + virtual void RegisterCommands() override + { + UI_COMMAND(GasaModule_Codegen_Command, "Run GasaGen Codegen", "Execute gasa codegen pass", EUserInterfaceActionType::Button, FInputChord()); + } + + TSharedPtr GasaModule_Codegen_Command; +}; + void FGasaEditorModule::StartupModule() { + // Codegen commands + { + FGasaEditorCommands::Register(); + + TSharedPtr PluginCommands = MakeShareable(new FUICommandList); + PluginCommands->MapAction( FGasaEditorCommands::Get().GasaModule_Codegen_Command, + FExecuteAction::CreateStatic(& Execute_GasaModule_Codegen), + FCanExecuteAction() + ); + + FLevelEditorModule& LEM = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr MenuExtender = MakeShareable(new FExtender); + MenuExtender->AddMenuExtension( + "Tools", + EExtensionHook::First, + PluginCommands, + FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& Builder) { + Builder.AddMenuEntry(FGasaEditorCommands::Get().GasaModule_Codegen_Command); + Builder.AddSeparator(); // Add a separator after your command + }) + ); + LEM.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + } + FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyEditor.RegisterCustomClassLayout( UGlobeProgressBar::StaticClass()->GetFName() @@ -22,4 +76,6 @@ void FGasaEditorModule::ShutdownModule() PropertyEditor.UnregisterCustomClassLayout(UGlobeProgressBar::StaticClass()->GetFName()); } } - \ No newline at end of file + +#undef LOCTEXT_NAMESPACE + diff --git a/Project/Source/GasaEditor/GasaEditorModule.h b/Project/Source/GasaEditor/GasaEditorModule.h index 0d43faa..0ea91f8 100644 --- a/Project/Source/GasaEditor/GasaEditorModule.h +++ b/Project/Source/GasaEditor/GasaEditorModule.h @@ -12,7 +12,7 @@ public: static FGasaEditorModule& Get() { return FModuleManager::LoadModuleChecked("GasaEditor"); } - + protected: void StartupModule() override; void ShutdownModule() override; diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen.cpp b/Project/Source/GasaEditor/GasaGen/GasaGen.cpp new file mode 100644 index 0000000..5583cf7 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen.cpp @@ -0,0 +1,114 @@ +#include "GasaGen.h" +#include "GasaGen_Common.h" +#include "GasaGen_DevOptionsCache.h" + +// Editor Module +#include "GasaEditorCommon.h" + +global String Project_Path; +global String Root_Path; + +global Code UHT_GENERATED_BODY; +global Code UHT_UCLASS; +global Code UHT_UPROPERTY; +global Code UHT_USTRUCT; +global Code UModule_GASA_API; + +void Execute_GasaModule_Codegen() +{ + Gasa::LogEditor("Executing: Gasa Module code generation."); + + gen::init(); + + FString ue_project_path = FPaths::ConvertRelativePathToFull( FPaths::ProjectDir() ); + FPaths::NormalizeDirectoryName(ue_project_path); + char const* ue_ansi_project_path = TCHAR_TO_ANSI( * ue_project_path); + + FString ue_root_path = FPaths::GetPath( ue_project_path ); + FPaths::NormalizeDirectoryName(ue_root_path); + char const* ue_ansi_rooot_path = TCHAR_TO_ANSI( * ue_project_path); + + Project_Path = String::make_length(GlobalAllocator, ue_ansi_project_path, ue_project_path.Len() ); + Root_Path = String::make_length(GlobalAllocator, ue_ansi_rooot_path, ue_root_path.Len() ); + UE_LOG(LogTemp, Log, TEXT("Current ROOT Directory: %s"), *ue_project_path); + UE_LOG(LogTemp, Log, TEXT("Current Project Directory: %s"), *ue_root_path); + + // Initialize Globals + { + #pragma push_macro("UCLASS") + #pragma push_macro("UPROPERTY") + #pragma push_macro("USTRUCT") + #pragma push_macro("GENERATED_BODY") + #pragma push_macro("GASA_API") + #undef UCLASS + #undef UPROPERTY + #undef USTRUCT + #undef GENERATED_BODY + #undef GASA_API + UHT_UCLASS = code_str( UCLASS() ); + UHT_UPROPERTY = code_str( UPROPERTY() ); + UHT_USTRUCT = code_str( USTRUCT() ); + UHT_GENERATED_BODY = code_str( GENERATED_BODY()\n ); + UModule_GASA_API = code_str( GASA_API ); + #pragma pop_macro("UCLASS") + #pragma pop_macro("UPROPERTY") + #pragma pop_macro("USTRUCT") + #pragma pop_macro("GENERATED_BODY") + #pragma pop_macro("GASA_API") + } + + // Populate Defines + { + PreprocessorDefines.append( get_cached_string(str_DECLARE_CLASS)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DELEGATE_RetVal_OneParam)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DELEGATE_RetVal_ThreeParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DELEGATE_SixParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_NineParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SevenParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_EVENT_ThreeParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_EVENT_TwoParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_FUNCTION)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_LOG_CATEGORY_EXTERN)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_MULTICAST_DELEGATE_OneParam)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_MULTICAST_DELEGATE_ThreeParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_MULTICAST_DELEGATE_TwoParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_TS_MULTICAST_DELEGATE_OneParam)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_TS_MULTICAST_DELEGATE_TwoParams)); + PreprocessorDefines.append( get_cached_string(str_DECLARE_TS_MULTICAST_DELEGATE_ThreeParams)); + PreprocessorDefines.append( get_cached_string(str_DEFINE_ACTORDESC_TYPE)); + PreprocessorDefines.append( get_cached_string(str_DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL)); + PreprocessorDefines.append( get_cached_string(str_ENUM_CLASS_FLAGS)); + PreprocessorDefines.append( get_cached_string(str_FORCEINLINE_DEBUGGABLE)); + // PreprocessorDefines.append( get_cached_string(str_FORCEINLINE)); + PreprocessorDefines.append( get_cached_string(str_GENERATED_BODY)); + PreprocessorDefines.append( get_cached_string(str_GENERATED_UCLASS_BODY)); + PreprocessorDefines.append( get_cached_string(str_GENERATED_USTRUCT_BODY)); + PreprocessorDefines.append( get_cached_string(str_PRAGMA_DISABLE_DEPRECATION_WARNINGS)); + PreprocessorDefines.append( get_cached_string(str_PRAGMA_ENABLE_DEPRECATION_WARNINGS)); + PreprocessorDefines.append( get_cached_string(str_PROPERTY_BINDING_IMPLEMENTATION)); + PreprocessorDefines.append( get_cached_string(str_RESULT_DECL)); + PreprocessorDefines.append( get_cached_string(str_SLATE_BEGIN_ARGS)); + PreprocessorDefines.append( get_cached_string(str_SLATE_END_ARGS)); + PreprocessorDefines.append( get_cached_string(str_TEXT)); + PreprocessorDefines.append( get_cached_string(str_UCLASS)); + PreprocessorDefines.append( get_cached_string(str_UENUM)); + PreprocessorDefines.append( get_cached_string(str_UFUNCTION)); + PreprocessorDefines.append( get_cached_string(str_UMETA)); + PreprocessorDefines.append( get_cached_string(str_UPARAM)); + PreprocessorDefines.append( get_cached_string(str_UPROPERTY)); + PreprocessorDefines.append( get_cached_string(str_USTRUCT)); + PreprocessorDefines.append( get_cached_string(str_UE_REQUIRES)); + } + + //generate_AttributeSets(); + generate_DevOptionsCache(); + //generate_HostWidgetController(); + + gen::deinit(); +} + diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen.h b/Project/Source/GasaEditor/GasaGen/GasaGen.h new file mode 100644 index 0000000..f128cb6 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen.h @@ -0,0 +1,3 @@ +#pragma once + +void Execute_GasaModule_Codegen(); diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.cpp b/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.cpp new file mode 100644 index 0000000..4d47d25 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.cpp @@ -0,0 +1,8 @@ +#include "GasaGen_AttributeSets.h" +#include "GasaGen_Common.h" + +void generate_AttributeSets() +{ + +} + diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.h b/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.h new file mode 100644 index 0000000..2eddf04 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen_AttributeSets.h @@ -0,0 +1,3 @@ +#pragma once + +void generate_attribute_sets(); diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen_Common.h b/Project/Source/GasaEditor/GasaGen/GasaGen_Common.h new file mode 100644 index 0000000..3a7099c --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen_Common.h @@ -0,0 +1,131 @@ +#pragma once + +#include "gencpp/gen.hpp" +#include "gencpp/gen.builder.hpp" +using namespace gen; + +#undef check + +// Codegen assumes its working directory is the project +#define path_scripts "/scripts/" +#define path_project "/Project/" +#define path_source "/Source/" +#define path_config path_source "Config/" +#define path_module_gasa path_source "Gasa/" +#define path_gasa_ability_system path_module_gasa "AbilitySystem/" +#define path_gasa_actors path_module_gasa "Actors/" +#define path_gasa_characters path_module_gasa "Characters/" +#define path_gasa_game path_module_gasa "Game/" +#define path_gasa_ui path_module_gasa "UI/" + +constexpr StrC str_DECLARE_CLASS = txt("DECLARE_CLASS("); +constexpr StrC str_DECLARE_DELEGATE_RetVal_OneParam = txt("DECLARE_DELEGATE_RetVal_OneParam("); +constexpr StrC str_DECLARE_DELEGATE_RetVal_ThreeParams = txt("DECLARE_DELEGATE_RetVal_ThreeParams("); +constexpr StrC str_DECLARE_DELEGATE_SixParams = txt("DECLARE_DELEGATE_SixParams("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam = txt("DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_NineParams = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_NineParams("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SevenParams = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SevenParams("); +constexpr StrC str_DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams = txt("DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams("); +constexpr StrC str_DECLARE_EVENT_ThreeParams = txt("DECLARE_EVENT_ThreeParams("); +constexpr StrC str_DECLARE_EVENT_TwoParams = txt("DECLARE_EVENT_TwoParams("); +constexpr StrC str_DECLARE_FUNCTION = txt("DECLARE_FUNCTION("); +constexpr StrC str_DECLARE_LOG_CATEGORY_EXTERN = txt("DECLARE_LOG_CATEGORY_EXTERN("); +constexpr StrC str_DECLARE_MULTICAST_DELEGATE_OneParam = txt("DECLARE_MULTICAST_DELEGATE_OneParam("); +constexpr StrC str_DECLARE_MULTICAST_DELEGATE_ThreeParams = txt("DECLARE_MULTICAST_DELEGATE_ThreeParams("); +constexpr StrC str_DECLARE_MULTICAST_DELEGATE_TwoParams = txt("DECLARE_MULTICAST_DELEGATE_TwoParams("); +constexpr StrC str_DECLARE_TS_MULTICAST_DELEGATE_OneParam = txt("DECLARE_TS_MULTICAST_DELEGATE_OneParam("); +constexpr StrC str_DECLARE_TS_MULTICAST_DELEGATE_TwoParams = txt("DECLARE_TS_MULTICAST_DELEGATE_TwoParams("); +constexpr StrC str_DECLARE_TS_MULTICAST_DELEGATE_ThreeParams = txt("DECLARE_TS_MULTICAST_DELEGATE_ThreeParams("); +constexpr StrC str_DEFINE_ACTORDESC_TYPE = txt("DEFINE_ACTORDESC_TYPE("); +constexpr StrC str_DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL = txt("DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL("); +constexpr StrC str_ENUM_CLASS_FLAGS = txt("ENUM_CLASS_FLAGS("); +// constexpr StrC str_FORCEINLINE = txt("FORCEINLINE"); +constexpr StrC str_FORCEINLINE_DEBUGGABLE = txt("FORCEINLINE_DEBUGGABLE"); +constexpr StrC str_GENERATED_BODY = txt("GENERATED_BODY("); +constexpr StrC str_GENERATED_UCLASS_BODY = txt("GENERATED_UCLASS_BODY("); +constexpr StrC str_GENERATED_USTRUCT_BODY = txt("GENERATED_USTRUCT_BODY("); +constexpr StrC str_PRAGMA_DISABLE_DEPRECATION_WARNINGS = txt("PRAGMA_DISABLE_DEPRECATION_WARNINGS"); +constexpr StrC str_PRAGMA_ENABLE_DEPRECATION_WARNINGS = txt("PRAGMA_ENABLE_DEPRECATION_WARNINGS"); +constexpr StrC str_PROPERTY_BINDING_IMPLEMENTATION = txt("PROPERTY_BINDING_IMPLEMENTATION("); +constexpr StrC str_RESULT_DECL = txt("RESULT_DECL"); +constexpr StrC str_SLATE_BEGIN_ARGS = txt("SLATE_BEGIN_ARGS("); +constexpr StrC str_SLATE_END_ARGS = txt("SLATE_END_ARGS("); +constexpr StrC str_TEXT = txt("TEXT("); +constexpr StrC str_UCLASS = txt("UCLASS("); +constexpr StrC str_UENUM = txt("UENUM("); +constexpr StrC str_UFUNCTION = txt("UFUNCTION("); +constexpr StrC str_UMETA = txt("UMETA("); +constexpr StrC str_UPARAM = txt("UPARAM("); +constexpr StrC str_UPROPERTY = txt("UPROPERTY("); +constexpr StrC str_USTRUCT = txt("USTRUCT("); +constexpr StrC str_UE_REQUIRES = txt("UE_REQUIRES("); + +#pragma region Globals +extern String Project_Path; +extern String Root_Path; + +// These Code objects are created before anything else after gencpp does its initializatioon +extern Code UHT_GENERATED_BODY; +extern Code UHT_UCLASS; +extern Code UHT_UPROPERTY; +extern Code UHT_USTRUCT; +extern Code UModule_GASA_API; +#pragma endregion Globals + +inline +CodeBody parse_file( char const* path ) { + String + resolved_path = String::make(GlobalAllocator, StrC(Project_Path)); + resolved_path.append(path); + + FileContents content = file_read_contents( GlobalAllocator, true, resolved_path ); + CodeBody code = parse_global_body( StrC { content.size, (char const*)content.data }); + return code; +} + +// Automatically handles resolving project path +inline +Builder builder_open(char const* path) { + String + resolved_path = String::make(GlobalAllocator, StrC(Project_Path)); + resolved_path.append(path); + return Builder::open( resolved_path ); +} + +// inline +// CodeConstructor find_constructor( StrC parent_name, ) + +inline +void format_file( char const* path ) +{ + String + resolved_path = String::make(GlobalAllocator, StrC(Project_Path)); + resolved_path.append(path); + + String clang_format_path = String::make(GlobalAllocator, StrC(Root_Path)); + clang_format_path.append("/scripts/.clang-format"); + + // Need to execute clang format on the generated file to get it to match the original. + #define clang_format "clang-format " + #define cf_format_inplace "-i " + #define cf_verbose "-verbose " + String command = String::make( GlobalAllocator, clang_format ); + command.append( cf_format_inplace ); + command.append_fmt("-style=file:%S ", *StrC(clang_format_path)); + command.append( cf_verbose ); + command.append( resolved_path ); + log_fmt("\tRunning clang-format on file:\n"); + system( command ); + log_fmt("\tclang-format finished reformatting.\n"); + + + FString command_fstr = FString( command.Data, command.length()); + UE_LOG(LogTemp, Log, TEXT("clang format command: %s"), *command_fstr ); + #undef cf_cmd + #undef cf_format_inplace + #undef cf_style + #undef cf_verbse +} diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.cpp b/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.cpp new file mode 100644 index 0000000..7057ff4 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.cpp @@ -0,0 +1,169 @@ +#include "GasaGen_DevOptionsCache.h" +#include "GasaGen_Common.h" + +#pragma push_macro("GASA_API") +#pragma push_macro("ensureMsgf") +#undef GASA_API +#undef ensureMsgf + + +PRAGMA_DISABLE_OPTIMIZATION +void generate_DevOptionsCache() +{ + Array GasaDevOptions_UPROPERTIES = Array::init(GlobalAllocator); + { + CodeBody header_GasaDevOptions = parse_file( path_module_gasa "GasaDevOptions.h" ); + CodeClass UGasaDevOptions = NoCode; + for (Code entry : header_GasaDevOptions) + { + if ( entry->Type == ECode::Class && entry->Name.starts_with( txt("UGasaDevOptions")) ) + { + UGasaDevOptions = entry.cast(); + break; + } + } + for (Code member = UGasaDevOptions->Body.begin(); member != UGasaDevOptions->Body.end(); ++ member) + { + if ( member->Type == ECode::Untyped && member->Name.starts_with(str_UPROPERTY) ) + ++ member; + if ( member->Type == ECode::Variable + && ( member->ValueType->Name.starts_with( txt("TArray< TSoftObjectPtr")) + || member->ValueType->Name.starts_with( txt("TSoftClassPtr")) + || member->ValueType->Name.starts_with( txt("TSoftObjectPtr")) ) + ) + GasaDevOptions_UPROPERTIES.append(member.cast()); + } + } + + CodeComment generation_notice = def_comment(txt("Generated by GasaGen/GasaGen_DevOptionsCache.cpp")); + + CodeType t_UClassPtr = parse_type(code(UClass*)); + CodeType t_UObjectPtr = parse_type(code(UObject*)); + CodeType t_Array_UObjectPtr = parse_type(code(TArray< UObject* >)); + + Builder header = builder_open( path_module_gasa "GasaDevOptionsCache.h" ); + { + header.print( generation_notice ); + header.print( pragma_once ); + header.print( fmt_newline ); + header.print( def_include(txt("GasaDevOptionsCache.generated.h"))); + header.print( fmt_newline ); + + header.print( UHT_USTRUCT ); + CodeStruct FGasaDevOptionsCache; + { + CodeBody body = def_body(ECode::Struct_Body); + { + body.append(UHT_GENERATED_BODY); + body.append(fmt_newline); + for (CodeVar var : GasaDevOptions_UPROPERTIES) + { + if ( var->ValueType->Name.starts_with( txt("TSoftClassPtr") )) { + body.append(UHT_UPROPERTY); + body.append( def_variable(t_UClassPtr, var->Name)); + } + if ( var->ValueType->Name.starts_with( txt("TSoftObjectPtr") )) { + body.append(UHT_UPROPERTY); + body.append( def_variable(t_UObjectPtr, var->Name)); + } + if ( var->ValueType->Name.starts_with( txt("TArray< TSoftObjectPtr") )) { + body.append(UHT_UPROPERTY); + body.append( def_variable(t_Array_UObjectPtr, var->Name)); + } + } + body.append(fmt_newline); + body.append( parse_function(code( void CachedDevOptions(); ))); + } + FGasaDevOptionsCache = parse_struct( token_fmt( "body", (StrC)body.to_string(), stringize( + struct GASA_API FGasaDevOptionsCache { + + }; + ))); + } + header.print(FGasaDevOptionsCache); + header.print( fmt_newline ); + header.write(); + format_file( path_module_gasa "GasaDevOptionsCache.h" ); + } + + Builder source = builder_open( path_module_gasa "GasaDevOptionsCache.cpp" ); + { + Array GasaDevOptions_Includes = Array::init(GlobalAllocator); + { + CodeBody source_GasaDevOptions = parse_file( path_module_gasa "GasaDevOptions.cpp"); + for ( Code entry : source_GasaDevOptions ) + { + if ( entry->Type == ECode::Preprocess_Include ) + GasaDevOptions_Includes.append( entry.cast() ); + } + } + + source.print( generation_notice ); + source.print( def_include(txt("GasaDevOptionsCache.h"))); + source.print(fmt_newline); + for ( CodeInclude include : GasaDevOptions_Includes ) { + source.print( include ); + } + source.print( parse_using(code( using namespace Gasa; ))); + source.print(fmt_newline); + + CodeBody cached_property_assignments = def_body(ECode::Function_Body); + { + cached_property_assignments.append(fmt_newline); + cached_property_assignments.append(fmt_newline); + for (CodeVar var : GasaDevOptions_UPROPERTIES) + { + if ( var->ValueType.to_string().starts_with(txt("TArray")) ) + { + #pragma push_macro("TEXT") + #undef TEXT + Code assignment = code_fmt( "property_array", (StrC)var->Name, stringize( + for ( auto& entry : DevOpts-> ) + { + .Push( entry.LoadSynchronous() ); + ensureMsgf(entry != nullptr, TEXT("An entry is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++")); + } + )); + #pragma pop_macro("TEXT") + cached_property_assignments.append(assignment); + cached_property_assignments.append(fmt_newline); + cached_property_assignments.append(fmt_newline); + + continue; + } + + #pragma push_macro("TEXT") + #undef TEXT + Code assignment = code_fmt( "property", (StrC)var->Name, stringize( + = DevOpts-> .LoadSynchronous(); + ensureMsgf( != nullptr, TEXT(" is null, DO NOT RUN PIE or else you may get a crash if not handled in BP or C++")); + )); + #pragma pop_macro("TEXT") + cached_property_assignments.append(assignment); + cached_property_assignments.append(fmt_newline); + cached_property_assignments.append(fmt_newline); + } + } + + CodeFn CachedDevOptions = parse_function( token_fmt( + "cached_property_assignments", (StrC)cached_property_assignments.to_string(), + stringize( + void FGasaDevOptionsCache::CachedDevOptions() + { + UGasaDevOptions* DevOpts = GetMutDevOptions(); + + + + Tag_GlobalPPV = DevOpts->Tag_GlobalPPV; + }) + )); + source.print(CachedDevOptions); + source.write(); + format_file( path_module_gasa "GasaDevOptionsCache.cpp" ); + } +} +PRAGMA_ENABLE_OPTIMIZATION + +#pragma pop_macro("ensureMsgf") +#pragma pop_macro("GASA_API") + diff --git a/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.h b/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.h new file mode 100644 index 0000000..46647b8 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/GasaGen_DevOptionsCache.h @@ -0,0 +1,3 @@ +#pragma once + +void generate_DevOptionsCache(); diff --git a/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.cpp b/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.cpp new file mode 100644 index 0000000..8fbbe89 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.cpp @@ -0,0 +1,88 @@ +// This file was generated automatially by gencpp's bootstrap.cpp (See: https://github.com/Ed94/gencpp) + +#include "gen.builder.hpp" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-const-variable" +#pragma clang diagnostic ignored "-Wswitch" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wvarargs" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wcomment" +#pragma GCC diagnostic ignored "-Wswitch" +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +GEN_NS_BEGIN + +Builder Builder::open( char const* path ) +{ + Builder result; + + FileError error = file_open_mode( &result.File, EFileMode_WRITE, path ); + if ( error != EFileError_NONE ) + { + log_failure( "gen::File::open - Could not open file: %s", path ); + return result; + } + result.Buffer = String::make_reserve( GlobalAllocator, Builder_StrBufferReserve ); + + // log_fmt("$Builder - Opened file: %s\n", result.File.filename ); + return result; +} + +void Builder::pad_lines( s32 num ) +{ + Buffer.append( "\n" ); +} + +void Builder::print( Code code ) +{ + String str = code->to_string(); + // const sw len = str.length(); + // log_fmt( "%s - print: %.*s\n", File.filename, len > 80 ? 80 : len, str.Data ); + Buffer.append( str ); +} + +void Builder::print_fmt( char const* fmt, ... ) +{ + sw res; + char buf[GEN_PRINTF_MAXLEN] = { 0 }; + + va_list va; + va_start( va, fmt ); + res = str_fmt_va( buf, count_of( buf ) - 1, fmt, va ) - 1; + va_end( va ); + + // log_fmt( "$%s - print_fmt: %.*s\n", File.filename, res > 80 ? 80 : res, buf ); + Buffer.append( buf, res ); +} + +void Builder::write() +{ + b32 result = file_write( &File, Buffer, Buffer.length() ); + + if ( result == false ) + log_failure( "gen::File::write - Failed to write to file: %s\n", file_name( &File ) ); + + log_fmt( "Generated: %s\n", File.filename ); + file_close( &File ); + Buffer.free(); +} + +GEN_NS_END + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.hpp b/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.hpp new file mode 100644 index 0000000..330647e --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/gencpp/gen.builder.hpp @@ -0,0 +1,50 @@ +// This file was generated automatially by gencpp's bootstrap.cpp (See: https://github.com/Ed94/gencpp) + +#pragma once + +#include "gen.hpp" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-const-variable" +#pragma clang diagnostic ignored "-Wswitch" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wvarargs" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wcomment" +#pragma GCC diagnostic ignored "-Wswitch" +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +GEN_NS_BEGIN + +struct Builder +{ + FileInfo File; + String Buffer; + + static Builder open( char const* path ); + + void pad_lines( s32 num ); + + void print( Code ); + void print_fmt( char const* fmt, ... ); + + void write(); +}; + +GEN_NS_END + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/Project/Source/GasaEditor/GasaGen/gencpp/gen.cpp b/Project/Source/GasaEditor/GasaGen/gencpp/gen.cpp new file mode 100644 index 0000000..7727533 --- /dev/null +++ b/Project/Source/GasaEditor/GasaGen/gencpp/gen.cpp @@ -0,0 +1,12865 @@ +// This file was generated automatially by gencpp's bootstrap.cpp (See: https://github.com/Ed94/gencpp) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-const-variable" +#pragma clang diagnostic ignored "-Wswitch" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wvarargs" +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wcomment" +#pragma GCC diagnostic ignored "-Wswitch" +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif +#if ! defined( GEN_DONT_ENFORCE_GEN_TIME_GUARD ) && ! defined( GEN_TIME ) +#error Gen.hpp : GEN_TIME not defined +#endif + +#include "gen.hpp" + +GEN_NS_BEGIN + +#pragma region StaticData + +// TODO : Convert global allocation strategy to use a slab allocation strategy. +global AllocatorInfo GlobalAllocator; +global Array Global_AllocatorBuckets; + +// TODO(Ed) : Make the code pool a dynamic arena +global Array CodePools = { nullptr }; +global Array StringArenas = { nullptr }; + +global StringTable StringCache; + +global Arena LexArena; + +global AllocatorInfo Allocator_DataArrays = heap(); +global AllocatorInfo Allocator_CodePool = heap(); +global AllocatorInfo Allocator_Lexer = heap(); +global AllocatorInfo Allocator_StringArena = heap(); +global AllocatorInfo Allocator_StringTable = heap(); +global AllocatorInfo Allocator_TypeTable = heap(); + +#pragma endregion StaticData + +#pragma region Constants + +global Code access_public; +global Code access_protected; +global Code access_private; + +global CodeAttributes attrib_api_export; +global CodeAttributes attrib_api_import; + +global Code module_global_fragment; +global Code module_private_fragment; + +global Code fmt_newline; + +global CodeParam param_varadic; + +global CodePragma pragma_once; + +global CodePreprocessCond preprocess_else; +global CodePreprocessCond preprocess_endif; + +global CodeSpecifiers spec_const; +global CodeSpecifiers spec_consteval; +global CodeSpecifiers spec_constexpr; +global CodeSpecifiers spec_constinit; +global CodeSpecifiers spec_extern_linkage; +global CodeSpecifiers spec_final; +global CodeSpecifiers spec_FORCEINLINE; +global CodeSpecifiers spec_global; +global CodeSpecifiers spec_inline; +global CodeSpecifiers spec_internal_linkage; +global CodeSpecifiers spec_local_persist; +global CodeSpecifiers spec_mutable; +global CodeSpecifiers spec_noexcept; +global CodeSpecifiers spec_neverinline; +global CodeSpecifiers spec_override; +global CodeSpecifiers spec_ptr; +global CodeSpecifiers spec_pure; +global CodeSpecifiers spec_ref; +global CodeSpecifiers spec_register; +global CodeSpecifiers spec_rvalue; +global CodeSpecifiers spec_static_member; +global CodeSpecifiers spec_thread_local; +global CodeSpecifiers spec_virtual; +global CodeSpecifiers spec_volatile; + +global CodeType t_empty; +global CodeType t_auto; +global CodeType t_void; +global CodeType t_int; +global CodeType t_bool; +global CodeType t_char; +global CodeType t_wchar_t; +global CodeType t_class; +global CodeType t_typename; + +global Array PreprocessorDefines; + +#ifdef GEN_DEFINE_LIBRARY_CODE_CONSTANTS +global CodeType t_b32; + +global CodeType t_s8; +global CodeType t_s16; +global CodeType t_s32; +global CodeType t_s64; + +global CodeType t_u8; +global CodeType t_u16; +global CodeType t_u32; +global CodeType t_u64; + +global CodeType t_sw; +global CodeType t_uw; + +global CodeType t_f32; +global CodeType t_f64; +#endif + +#pragma endregion Constants + +#pragma region AST + +#define GEN_AST_BODY_CLASS_UNALLOWED_TYPES \ + case PlatformAttributes : \ + case Class_Body : \ + case Enum_Body : \ + case Extern_Linkage : \ + case Function_Body : \ + case Function_Fwd : \ + case Global_Body : \ + case Namespace : \ + case Namespace_Body : \ + case Operator : \ + case Operator_Fwd : \ + case Parameters : \ + case Specifiers : \ + case Struct_Body : \ + case Typename : +#define GEN_AST_BODY_STRUCT_UNALLOWED_TYPES GEN_AST_BODY_CLASS_UNALLOWED_TYPES + +#define GEN_AST_BODY_FUNCTION_UNALLOWED_TYPES \ + case Access_Public : \ + case Access_Protected : \ + case Access_Private : \ + case PlatformAttributes : \ + case Class_Body : \ + case Enum_Body : \ + case Extern_Linkage : \ + case Friend : \ + case Function_Body : \ + case Function_Fwd : \ + case Global_Body : \ + case Namespace : \ + case Namespace_Body : \ + case Operator : \ + case Operator_Fwd : \ + case Operator_Member : \ + case Operator_Member_Fwd : \ + case Parameters : \ + case Specifiers : \ + case Struct_Body : \ + case Typename : + +#define GEN_AST_BODY_GLOBAL_UNALLOWED_TYPES \ + case Access_Public : \ + case Access_Protected : \ + case Access_Private : \ + case PlatformAttributes : \ + case Class_Body : \ + case Enum_Body : \ + case Execution : \ + case Friend : \ + case Function_Body : \ + case Namespace_Body : \ + case Operator_Member : \ + case Operator_Member_Fwd : \ + case Parameters : \ + case Specifiers : \ + case Struct_Body : \ + case Typename : +#define GEN_AST_BODY_EXPORT_UNALLOWED_TYPES GEN_AST_BODY_GLOBAL_UNALLOWED_TYPES +#define GEN_AST_BODY_EXTERN_LINKAGE_UNALLOWED_TYPES GEN_AST_BODY_GLOBAL_UNALLOWED_TYPES + +#define GEN_AST_BODY_NAMESPACE_UNALLOWED_TYPES \ + case Access_Public : \ + case Access_Protected : \ + case Access_Private : \ + case PlatformAttributes : \ + case Class_Body : \ + case Enum_Body : \ + case Execution : \ + case Friend : \ + case Function_Body : \ + case Namespace_Body : \ + case Operator_Member : \ + case Operator_Member_Fwd : \ + case Parameters : \ + case Specifiers : \ + case Struct_Body : \ + case Typename : + +Code Code::Global; +Code Code::Invalid; + +// This serializes all the data-members in a "debug" format, where each member is printed with its associated value. +char const* AST::debug_str() +{ + String result = String::make_reserve( GlobalAllocator, kilobytes( 1 ) ); + + if ( Parent ) + result.append_fmt( "\n\tParent : %S %S", Parent->type_str(), Name ? Name : "" ); + else + result.append_fmt( "\n\tParent : %S", "Null" ); + + result.append_fmt( "\n\tName : %S", Name ? Name : "Null" ); + result.append_fmt( "\n\tType : %S", type_str() ); + result.append_fmt( "\n\tModule Flags : %S", to_str( ModuleFlags ) ); + + switch ( Type ) + { + using namespace ECode; + + case Invalid : + case NewLine : + case Access_Private : + case Access_Protected : + case Access_Public : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + break; + + case Untyped : + case Execution : + case Comment : + case PlatformAttributes : + case Preprocess_Define : + case Preprocess_Include : + case Preprocess_Pragma : + case Preprocess_If : + case Preprocess_ElIf : + case Preprocess_Else : + case Preprocess_IfDef : + case Preprocess_IfNotDef : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tContent: %S", Content ); + break; + + case Class : + case Struct : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmd : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tParentAccess: %s", ParentType ? to_str( ParentAccess ) : "No Parent" ); + result.append_fmt( "\n\tParentType : %s", ParentType ? ParentType->type_str() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Class_Fwd : + case Struct_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmd : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tParentAccess: %s", ParentType ? to_str( ParentAccess ) : "No Parent" ); + result.append_fmt( "\n\tParentType : %s", ParentType ? ParentType->type_str() : "Null" ); + break; + + case Constructor : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tInitializerList: %S", InitializerList ? InitializerList->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Constructor_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tInitializerList: %S", InitializerList ? InitializerList->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + break; + + case Destructor : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Destructor_Fwd : + break; + + case Enum : + case Enum_Class : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tUnderlying Type : %S", UnderlyingType ? UnderlyingType->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Enum_Fwd : + case Enum_Class_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tUnderlying Type : %S", UnderlyingType ? UnderlyingType->to_string() : "Null" ); + break; + + case Extern_Linkage : + case Namespace : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tBody: %S", Body ? Body->debug_str() : "Null" ); + break; + + case Friend : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tDeclaration: %S", Declaration ? Declaration->to_string() : "Null" ); + break; + + case Function : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes: %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tReturnType: %S", ReturnType ? ReturnType->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Function_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes: %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tReturnType: %S", ReturnType ? ReturnType->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + break; + + case Module : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + break; + + case Operator : + case Operator_Member : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes: %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tReturnType: %S", ReturnType ? ReturnType->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + result.append_fmt( "\n\tOp : %S", to_str( Op ) ); + break; + + case Operator_Fwd : + case Operator_Member_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes: %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tReturnType: %S", ReturnType ? ReturnType->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tOp : %S", to_str( Op ) ); + break; + + case Operator_Cast : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tValueType : %S", ValueType ? ValueType->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Operator_Cast_Fwd : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tValueType : %S", ValueType ? ValueType->to_string() : "Null" ); + break; + + case Parameters : + result.append_fmt( "\n\tNumEntries: %d", NumEntries ); + result.append_fmt( "\n\tLast : %S", Last->Name ); + result.append_fmt( "\n\tNext : %S", Next->Name ); + result.append_fmt( "\n\tValueType : %S", ValueType ? ValueType->to_string() : "Null" ); + result.append_fmt( "\n\tValue : %S", Value ? Value->to_string() : "Null" ); + break; + + case Specifiers : + { + result.append_fmt( "\n\tNumEntries: %d", NumEntries ); + result.append( "\n\tArrSpecs: " ); + + s32 idx = 0; + s32 left = NumEntries; + while ( left-- ) + { + StrC spec = ESpecifier::to_str( ArrSpecs[idx] ); + result.append_fmt( "%.*s, ", spec.Len, spec.Ptr ); + idx++; + } + result.append_fmt( "\n\tNextSpecs: %S", NextSpecs ? NextSpecs->debug_str() : "Null" ); + } + break; + + case Template : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tDeclaration: %S", Declaration ? Declaration->to_string() : "Null" ); + break; + + case Typedef : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tUnderlyingType: %S", UnderlyingType ? UnderlyingType->to_string() : "Null" ); + break; + + case Typename : + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tReturnType : %S", ReturnType ? ReturnType->to_string() : "Null" ); + result.append_fmt( "\n\tParams : %S", Params ? Params->to_string() : "Null" ); + result.append_fmt( "\n\tArrExpr : %S", ArrExpr ? ArrExpr->to_string() : "Null" ); + break; + + case Union : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tAttributes: %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tBody : %S", Body ? Body->debug_str() : "Null" ); + break; + + case Using : + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tUnderlyingType: %S", UnderlyingType ? UnderlyingType->to_string() : "Null" ); + break; + + case Variable : + + if ( Parent && Parent->Type == Variable ) + { + // Its a NextVar + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tValue : %S", Value ? Value->to_string() : "Null" ); + result.append_fmt( "\n\tBitfieldSize: %S", BitfieldSize ? BitfieldSize->to_string() : "Null" ); + result.append_fmt( "\n\tNextVar : %S", NextVar ? NextVar->debug_str() : "Null" ); + break; + } + + if ( Prev ) + result.append_fmt( "\n\tPrev: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + if ( Next ) + result.append_fmt( "\n\tNext: %S %S", Prev->type_str(), Prev->Name ? Prev->Name : "Null" ); + + result.append_fmt( "\n\tInlineCmt : %S", InlineCmt ? InlineCmt->Content : "Null" ); + result.append_fmt( "\n\tAttributes : %S", Attributes ? Attributes->to_string() : "Null" ); + result.append_fmt( "\n\tSpecs : %S", Specs ? Specs->to_string() : "Null" ); + result.append_fmt( "\n\tValueType : %S", ValueType ? ValueType->to_string() : "Null" ); + result.append_fmt( "\n\tBitfieldSize: %S", BitfieldSize ? BitfieldSize->to_string() : "Null" ); + result.append_fmt( "\n\tValue : %S", Value ? Value->to_string() : "Null" ); + result.append_fmt( "\n\tNextVar : %S", NextVar ? NextVar->debug_str() : "Null" ); + break; + } + + return result; +} + +AST* AST::duplicate() +{ + using namespace ECode; + + AST* result = make_code().ast; + + mem_copy( result, this, sizeof( AST ) ); + + result->Parent = nullptr; + return result; +} + +String AST::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void AST::to_string( String& result ) +{ + local_persist thread_local char SerializationLevel = 0; + + switch ( Type ) + { + using namespace ECode; + + case Invalid : +#ifdef GEN_DONT_ALLOW_INVALID_CODE + log_failure( "Attempted to serialize invalid code! - %S", Parent ? Parent->debug_str() : Name ); +#else + result.append_fmt( "Invalid Code!" ); +#endif + break; + + case NewLine : + result.append( "\n" ); + break; + + case Untyped : + case Execution : + case Comment : + case PlatformAttributes : + result.append( Content ); + break; + + case Access_Private : + case Access_Protected : + case Access_Public : + result.append( Name ); + break; + + case Class : + cast().to_string_def( result ); + break; + + case Class_Fwd : + cast().to_string_fwd( result ); + break; + + case Constructor : + cast().to_string_def( result ); + break; + + case Constructor_Fwd : + cast().to_string_fwd( result ); + break; + + case Destructor : + cast().to_string_def( result ); + break; + + case Destructor_Fwd : + cast().to_string_fwd( result ); + break; + + case Enum : + cast().to_string_def( result ); + break; + + case Enum_Fwd : + cast().to_string_fwd( result ); + break; + + case Enum_Class : + cast().to_string_class_def( result ); + break; + + case Enum_Class_Fwd : + cast().to_string_class_fwd( result ); + break; + + case Export_Body : + cast().to_string_export( result ); + break; + + case Extern_Linkage : + cast().to_string( result ); + break; + + case Friend : + cast().to_string( result ); + break; + + case Function : + cast().to_string_def( result ); + break; + + case Function_Fwd : + cast().to_string_fwd( result ); + break; + + case Module : + cast().to_string( result ); + break; + + case Namespace : + cast().to_string( result ); + break; + + case Operator : + case Operator_Member : + cast().to_string_def( result ); + break; + + case Operator_Fwd : + case Operator_Member_Fwd : + cast().to_string_fwd( result ); + break; + + case Operator_Cast : + cast().to_string_def( result ); + break; + + case Operator_Cast_Fwd : + cast().to_string_fwd( result ); + break; + + case Parameters : + cast().to_string( result ); + break; + + case Preprocess_Define : + cast().to_string( result ); + break; + + case Preprocess_If : + cast().to_string_if( result ); + break; + + case Preprocess_IfDef : + cast().to_string_ifdef( result ); + break; + + case Preprocess_IfNotDef : + cast().to_string_ifndef( result ); + break; + + case Preprocess_Include : + cast().to_string( result ); + break; + + case Preprocess_ElIf : + cast().to_string_elif( result ); + break; + + case Preprocess_Else : + cast().to_string_else( result ); + break; + + case Preprocess_EndIf : + cast().to_string_endif( result ); + break; + + case Preprocess_Pragma : + cast().to_string( result ); + break; + + case Specifiers : + cast().to_string( result ); + break; + + case Struct : + cast().to_string_def( result ); + break; + + case Struct_Fwd : + cast().to_string_fwd( result ); + break; + + case Template : + cast().to_string( result ); + break; + + case Typedef : + cast().to_string( result ); + break; + + case Typename : + cast().to_string( result ); + break; + + case Union : + cast().to_string( result ); + break; + + case Using : + cast().to_string( result ); + break; + + case Using_Namespace : + cast().to_string_ns( result ); + break; + + case Variable : + cast().to_string( result ); + break; + + case Enum_Body : + case Class_Body : + case Extern_Linkage_Body : + case Function_Body : + case Global_Body : + case Namespace_Body : + case Struct_Body : + case Union_Body : + cast().to_string( result ); + break; + } +} + +bool AST::is_equal( AST* other ) +{ + /* + AST values are either some u32 value, a cached string, or a pointer to another AST. + + u32 values are compared by value. + Cached strings are compared by pointer. + AST nodes are compared with AST::is_equal. + */ + if ( other == nullptr ) + { + log_fmt( "AST::is_equal: other is null\nAST: %S", debug_str() ); + return false; + } + + if ( Type != other->Type ) + { + log_fmt( "AST::is_equal: Type check failure with other\nAST: %S\nOther: %S", debug_str(), other->debug_str() ); + + return false; + } + + switch ( Type ) + { + using namespace ECode; + +#define check_member_val( val ) \ + if ( val != other->val ) \ + { \ + log_fmt( \ + "\nAST::is_equal: Member - " #val \ + " failed\n" \ + "AST : %S\n" \ + "Other: %S\n", \ + debug_str(), \ + other->debug_str() \ + ); \ + \ + return false; \ + } + +#define check_member_str( str ) \ + if ( str != other->str ) \ + { \ + log_fmt( \ + "\nAST::is_equal: Member string - " #str \ + " failed\n" \ + "AST : %S\n" \ + "Other: %S\n", \ + debug_str(), \ + other->debug_str() \ + ); \ + \ + return false; \ + } + +#define check_member_content( content ) \ + if ( content != other->content ) \ + { \ + log_fmt( \ + "\nAST::is_equal: Member content - " #content \ + " failed\n" \ + "AST : %S\n" \ + "Other: %S\n", \ + debug_str(), \ + other->debug_str() \ + ); \ + \ + log_fmt( \ + "Content cannot be trusted to be unique with this check " \ + "so it must be verified by eye for now\n" \ + "AST Content:\n%S\n" \ + "Other Content:\n%S\n", \ + content.visualize_whitespace(), \ + other->content.visualize_whitespace() \ + ); \ + } + +#define check_member_ast( ast ) \ + if ( ast ) \ + { \ + if ( other->ast == nullptr ) \ + { \ + log_fmt( \ + "\nAST::is_equal: Failed for member " #ast \ + " other equivalent param is null\n" \ + "AST : %s\n" \ + "Other: %s\n" \ + "For ast member: %s\n", \ + debug_str(), \ + other->debug_str(), \ + ast->debug_str() \ + ); \ + \ + return false; \ + } \ + \ + if ( ! ast->is_equal( other->ast ) ) \ + { \ + log_fmt( \ + "\nAST::is_equal: Failed for " #ast \ + "\n" \ + "AST : %S\n" \ + "Other: %S\n" \ + "For ast member: %S\n" \ + "other's ast member: %S\n", \ + debug_str(), \ + other->debug_str(), \ + ast->debug_str(), \ + other->ast->debug_str() \ + ); \ + \ + return false; \ + } \ + } + + case NewLine : + case Access_Public : + case Access_Protected : + case Access_Private : + case Preprocess_Else : + case Preprocess_EndIf : + return true; + + + // Comments are not validated. + case Comment : + return true; + + case Execution : + case PlatformAttributes : + case Untyped : + { + check_member_content( Content ); + + return true; + } + + case Class_Fwd : + case Struct_Fwd : + { + check_member_str( Name ); + check_member_ast( ParentType ); + check_member_val( ParentAccess ); + check_member_ast( Attributes ); + + return true; + } + + case Class : + case Struct : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ParentType ); + check_member_val( ParentAccess ); + check_member_ast( Attributes ); + check_member_ast( Body ); + + return true; + } + + case Constructor : + { + check_member_ast( InitializerList ); + check_member_ast( Params ); + check_member_ast( Body ); + + return true; + } + + case Constructor_Fwd : + { + check_member_ast( InitializerList ); + check_member_ast( Params ); + + return true; + } + + case Destructor : + { + check_member_ast( Specs ); + check_member_ast( Body ); + + return true; + } + + case Destructor_Fwd : + { + check_member_ast( Specs ); + + return true; + } + + case Enum : + case Enum_Class : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Attributes ); + check_member_ast( UnderlyingType ); + check_member_ast( Body ); + + return true; + } + + case Enum_Fwd : + case Enum_Class_Fwd : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Attributes ); + check_member_ast( UnderlyingType ); + + return true; + } + + case Extern_Linkage : + { + check_member_str( Name ); + check_member_ast( Body ); + + return true; + } + + case Friend : + { + check_member_str( Name ); + check_member_ast( Declaration ); + + return true; + } + + case Function : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ReturnType ); + check_member_ast( Attributes ); + check_member_ast( Specs ); + check_member_ast( Params ); + check_member_ast( Body ); + + return true; + } + + case Function_Fwd : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ReturnType ); + check_member_ast( Attributes ); + check_member_ast( Specs ); + check_member_ast( Params ); + + return true; + } + + case Module : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + + return true; + } + + case Namespace : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Body ); + + return true; + } + + case Operator : + case Operator_Member : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ReturnType ); + check_member_ast( Attributes ); + check_member_ast( Specs ); + check_member_ast( Params ); + check_member_ast( Body ); + + return true; + } + + case Operator_Fwd : + case Operator_Member_Fwd : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ReturnType ); + check_member_ast( Attributes ); + check_member_ast( Specs ); + check_member_ast( Params ); + + return true; + } + + case Operator_Cast : + { + check_member_str( Name ); + check_member_ast( Specs ); + check_member_ast( ValueType ); + check_member_ast( Body ); + + return true; + } + + case Operator_Cast_Fwd : + { + check_member_str( Name ); + check_member_ast( Specs ); + check_member_ast( ValueType ); + + return true; + } + + case Parameters : + { + if ( NumEntries > 1 ) + { + AST* curr = this; + AST* curr_other = other; + while ( curr != nullptr ) + { + if ( curr ) + { + if ( curr_other == nullptr ) + { + log_fmt( + "\nAST::is_equal: Failed for parameter, other equivalent param is null\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n", + curr->debug_str() + ); + + return false; + } + + if ( curr->Name != curr_other->Name ) + { + log_fmt( + "\nAST::is_equal: Failed for parameter name check\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n" + "other's ast member: %S\n", + debug_str(), + other->debug_str(), + curr->debug_str(), + curr_other->debug_str() + ); + return false; + } + + if ( curr->ValueType && ! curr->ValueType->is_equal( curr_other->ValueType ) ) + { + log_fmt( + "\nAST::is_equal: Failed for parameter value type check\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n" + "other's ast member: %S\n", + debug_str(), + other->debug_str(), + curr->debug_str(), + curr_other->debug_str() + ); + return false; + } + + if ( curr->Value && ! curr->Value->is_equal( curr_other->Value ) ) + { + log_fmt( + "\nAST::is_equal: Failed for parameter value check\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n" + "other's ast member: %S\n", + debug_str(), + other->debug_str(), + curr->debug_str(), + curr_other->debug_str() + ); + return false; + } + } + + curr = curr->Next; + curr_other = curr_other->Next; + } + + check_member_val( NumEntries ); + + return true; + } + + check_member_str( Name ); + check_member_ast( ValueType ); + check_member_ast( Value ); + check_member_ast( ArrExpr ); + + return true; + } + + case Preprocess_Define : + { + check_member_str( Name ); + check_member_content( Content ); + + return true; + } + + case Preprocess_If : + case Preprocess_IfDef : + case Preprocess_IfNotDef : + case Preprocess_ElIf : + { + check_member_content( Content ); + + return true; + } + + case Preprocess_Include : + case Preprocess_Pragma : + { + check_member_content( Content ); + + return true; + } + + case Specifiers : + { + check_member_val( NumEntries ); + check_member_str( Name ); + for ( s32 idx = 0; idx < NumEntries; ++idx ) + { + check_member_val( ArrSpecs[idx] ); + } + return true; + } + + case Template : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Params ); + check_member_ast( Declaration ); + + return true; + } + + case Typedef : + { + check_member_val( IsFunction ); + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Specs ); + check_member_ast( UnderlyingType ); + + return true; + } + case Typename : + { + check_member_val( IsParamPack ); + check_member_str( Name ); + check_member_ast( Specs ); + check_member_ast( ArrExpr ); + + return true; + } + + case Union : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( Attributes ); + check_member_ast( Body ); + + return true; + } + + case Using : + case Using_Namespace : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( UnderlyingType ); + check_member_ast( Attributes ); + + return true; + } + + case Variable : + { + check_member_val( ModuleFlags ); + check_member_str( Name ); + check_member_ast( ValueType ); + check_member_ast( BitfieldSize ); + check_member_ast( Value ); + check_member_ast( Attributes ); + check_member_ast( Specs ); + check_member_ast( NextVar ); + + return true; + } + + case Class_Body : + case Enum_Body : + case Export_Body : + case Global_Body : + case Namespace_Body : + case Struct_Body : + case Union_Body : + { + check_member_ast( Front ); + check_member_ast( Back ); + + AST* curr = Front; + AST* curr_other = other->Front; + while ( curr != nullptr ) + { + if ( curr_other == nullptr ) + { + log_fmt( + "\nAST::is_equal: Failed for body, other equivalent param is null\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n", + curr->debug_str() + ); + + return false; + } + + if ( ! curr->is_equal( curr_other ) ) + { + log_fmt( + "\nAST::is_equal: Failed for body\n" + "AST : %S\n" + "Other: %S\n" + "For ast member: %S\n" + "other's ast member: %S\n", + debug_str(), + other->debug_str(), + curr->debug_str(), + curr_other->debug_str() + ); + + return false; + } + + curr = curr->Next; + curr_other = curr_other->Next; + } + + check_member_val( NumEntries ); + + return true; + } + +#undef check_member_val +#undef check_member_str +#undef check_member_ast + } + + return true; +} + +bool AST::validate_body() +{ + using namespace ECode; + +#define CheckEntries( Unallowed_Types ) \ + do \ + { \ + for ( Code entry : cast() ) \ + { \ + switch ( entry->Type ) \ + { \ + Unallowed_Types log_failure( "AST::validate_body: Invalid entry in body %s", entry.debug_str() ); \ + return false; \ + } \ + } \ + } while ( 0 ); + + switch ( Type ) + { + case Class_Body : + CheckEntries( GEN_AST_BODY_CLASS_UNALLOWED_TYPES ); + break; + case Enum_Body : + for ( Code entry : cast() ) + { + if ( entry->Type != Untyped ) + { + log_failure( "AST::validate_body: Invalid entry in enum body (needs to be untyped or comment) %s", entry.debug_str() ); + return false; + } + } + break; + case Export_Body : + CheckEntries( GEN_AST_BODY_CLASS_UNALLOWED_TYPES ); + break; + case Extern_Linkage : + CheckEntries( GEN_AST_BODY_EXTERN_LINKAGE_UNALLOWED_TYPES ); + break; + case Function_Body : + CheckEntries( GEN_AST_BODY_FUNCTION_UNALLOWED_TYPES ); + break; + case Global_Body : + for ( Code entry : cast() ) + { + switch ( entry->Type ) + { + case Access_Public : + case Access_Protected : + case Access_Private : + case PlatformAttributes : + case Class_Body : + case Enum_Body : + case Execution : + case Friend : + case Function_Body : + case Global_Body : + case Namespace_Body : + case Operator_Member : + case Operator_Member_Fwd : + case Parameters : + case Specifiers : + case Struct_Body : + case Typename : + log_failure( "AST::validate_body: Invalid entry in body %s", entry.debug_str() ); + return false; + } + } + break; + case Namespace_Body : + CheckEntries( GEN_AST_BODY_NAMESPACE_UNALLOWED_TYPES ); + break; + case Struct_Body : + CheckEntries( GEN_AST_BODY_STRUCT_UNALLOWED_TYPES ); + break; + case Union_Body : + for ( Code entry : Body->cast() ) + { + if ( entry->Type != Untyped ) + { + log_failure( "AST::validate_body: Invalid entry in union body (needs to be untyped or comment) %s", entry.debug_str() ); + return false; + } + } + break; + + default : + log_failure( "AST::validate_body: Invalid this AST does not have a body %s", debug_str() ); + return false; + } + + return false; + +#undef CheckEntries +} + +String Code::to_string() +{ + if ( ast == nullptr ) + { + log_failure( "Code::to_string: Cannot convert code to string, AST is null!" ); + return { nullptr }; + } + return rcast( AST*, ast )->to_string(); +} + +String CodeAttributes::to_string() +{ + return ast->Content.duplicate( GlobalAllocator ); +} + +String CodeBody::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Untyped : + case Execution : + result.append( raw()->Content ); + break; + + case Enum_Body : + case Class_Body : + case Extern_Linkage_Body : + case Function_Body : + case Global_Body : + case Namespace_Body : + case Struct_Body : + case Union_Body : + to_string( result ); + break; + + case Export_Body : + to_string_export( result ); + break; + } + return result; +} + +void CodeBody::to_string( String& result ) +{ + Code curr = ast->Front; + s32 left = ast->NumEntries; + while ( left-- ) + { + result.append_fmt( "%S", curr.to_string() ); + ++curr; + } +} + +void CodeBody::to_string_export( String& result ) +{ + result.append_fmt( "export\n{\n" ); + + Code curr = *this; + s32 left = ast->NumEntries; + while ( left-- ) + { + result.append_fmt( "%S", curr.to_string() ); + ++curr; + } + + result.append_fmt( "};\n" ); +} + +String CodeComment::to_string() +{ + return ast->Content.duplicate( GlobalAllocator ); +} + +String CodeConstructor::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Constructor : + to_string_def( result ); + break; + case Constructor_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeConstructor::to_string_def( String& result ) +{ + AST* ClassStructParent = ast->Parent->Parent; + if ( ClassStructParent ) + { + result.append( ClassStructParent->Name ); + } + else + { + result.append( ast->Name ); + } + + if ( ast->Params ) + result.append_fmt( "( %S )", ast->Params.to_string() ); + else + result.append( "()" ); + + if ( ast->InitializerList ) + result.append_fmt( " : %S", ast->InitializerList.to_string() ); + + if ( ast->InlineCmt ) + result.append_fmt( " // %S", ast->InlineCmt->Content ); + + result.append_fmt( "\n{\n%S\n}\n", ast->Body.to_string() ); +} + +void CodeConstructor::to_string_fwd( String& result ) +{ + AST* ClassStructParent = ast->Parent->Parent; + if ( ClassStructParent ) + { + result.append( ClassStructParent->Name ); + } + else + { + result.append( ast->Name ); + } + + if ( ast->Params ) + result.append_fmt( "( %S )", ast->Params.to_string() ); + else + result.append_fmt( "()" ); + + if ( ast->Body ) + result.append_fmt( " = %S", ast->Body.to_string() ); + + if ( ast->InlineCmt ) + result.append_fmt( "; // %S\n", ast->InlineCmt->Content ); + else + result.append( ";\n" ); +} + +String CodeClass::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Class : + to_string_def( result ); + break; + case Class_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeClass::to_string_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append( "class " ); + + if ( ast->Attributes ) + { + result.append_fmt( "%S ", ast->Attributes.to_string() ); + } + + if ( ast->ParentType ) + { + char const* access_level = to_str( ast->ParentAccess ); + + result.append_fmt( "%S : %s %S", ast->Name, access_level, ast->ParentType.to_string() ); + + CodeType interface = ast->ParentType->Next->cast(); + if ( interface ) + result.append( "\n" ); + + while ( interface ) + { + result.append_fmt( ", %S", interface.to_string() ); + interface = interface->Next ? interface->Next->cast() : CodeType { nullptr }; + } + } + else if ( ast->Name ) + { + result.append( ast->Name ); + } + + if ( ast->InlineCmt ) + { + result.append_fmt( " // %S", ast->InlineCmt->Content ); + } + + result.append_fmt( "\n{\n%S\n}", ast->Body.to_string() ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + result.append( ";\n" ); +} + +void CodeClass::to_string_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "class %S %S", ast->Attributes.to_string(), ast->Name ); + + else + result.append_fmt( "class %S", ast->Name ); + + // Check if it can have an end-statement + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + { + if ( ast->InlineCmt ) + result.append_fmt( "; // %S\n", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + } +} + +String CodeDefine::to_string() +{ + return String::fmt_buf( GlobalAllocator, "#define %S %S\n", ast->Name, ast->Content ); +} + +void CodeDefine::to_string( String& result ) +{ + result.append_fmt( "#define %S %S\n", ast->Name, ast->Content ); +} + +String CodeDestructor::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Destructor : + to_string_def( result ); + break; + case Destructor_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeDestructor::to_string_def( String& result ) +{ + if ( ast->Name ) + { + result.append_fmt( "%S()", ast->Name ); + } + else if ( ast->Specs ) + { + if ( ast->Specs.has( ESpecifier::Virtual ) ) + result.append_fmt( "virtual ~%S()", ast->Parent->Name ); + else + result.append_fmt( "~%S()", ast->Parent->Name ); + } + else + result.append_fmt( "~%S()", ast->Parent->Name ); + + result.append_fmt( "\n{\n%S\n}\n", ast->Body.to_string() ); +} + +void CodeDestructor::to_string_fwd( String& result ) +{ + if ( ast->Specs ) + { + if ( ast->Specs.has( ESpecifier::Virtual ) ) + result.append_fmt( "virtual ~%S();\n", ast->Parent->Name ); + else + result.append_fmt( "~%S()", ast->Parent->Name ); + + if ( ast->Specs.has( ESpecifier::Pure ) ) + result.append( " = 0;" ); + else if ( ast->Body ) + result.append_fmt( " = %S;", ast->Body.to_string() ); + } + else + result.append_fmt( "~%S();", ast->Parent->Name ); + + if ( ast->InlineCmt ) + result.append_fmt( " %S", ast->InlineCmt->Content ); + else + result.append( "\n" ); +} + +String CodeEnum::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Enum : + to_string_def( result ); + break; + case Enum_Fwd : + to_string_fwd( result ); + break; + case Enum_Class : + to_string_class_def( result ); + break; + case Enum_Class_Fwd : + to_string_class_fwd( result ); + break; + } + return result; +} + +void CodeEnum::to_string_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes || ast->UnderlyingType ) + { + result.append( "enum " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->UnderlyingType ) + result.append_fmt( "%S : %S\n{\n%S\n}", ast->Name, ast->UnderlyingType.to_string(), ast->Body.to_string() ); + + else + result.append_fmt( "%S\n{\n%S\n}", ast->Name, ast->Body.to_string() ); + } + else + result.append_fmt( "enum %S\n{\n%S\n}", ast->Name, ast->Body.to_string() ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + result.append( ";\n" ); +} + +void CodeEnum::to_string_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + result.append_fmt( "enum %S : %S", ast->Name, ast->UnderlyingType.to_string() ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + { + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + } +} + +void CodeEnum::to_string_class_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes || ast->UnderlyingType ) + { + result.append( "enum class " ); + + if ( ast->Attributes ) + { + result.append_fmt( "%S ", ast->Attributes.to_string() ); + } + + if ( ast->UnderlyingType ) + { + result.append_fmt( "%S : %S\n{\n%S\n}", ast->Name, ast->UnderlyingType.to_string(), ast->Body.to_string() ); + } + else + { + result.append_fmt( "%S\n{\n%S\n}", ast->Name, ast->Body.to_string() ); + } + } + else + { + result.append_fmt( "enum class %S\n{\n%S\n}", ast->Body.to_string() ); + } + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + result.append( ";\n" ); +} + +void CodeEnum::to_string_class_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append( "enum class " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + result.append_fmt( "%S : %S", ast->Name, ast->UnderlyingType.to_string() ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + { + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + } +} + +String CodeExec::to_string() +{ + return ast->Content.duplicate( GlobalAllocator ); +} + +void CodeExtern::to_string( String& result ) +{ + if ( ast->Body ) + result.append_fmt( "extern \"%S\"\n{\n%S\n}\n", ast->Name, ast->Body.to_string() ); + else + result.append_fmt( "extern \"%S\"\n{}\n", ast->Name ); +} + +String CodeInclude::to_string() +{ + return String::fmt_buf( GlobalAllocator, "#include %S\n", ast->Content ); +} + +void CodeInclude::to_string( String& result ) +{ + result.append_fmt( "#include %S\n", ast->Content ); +} + +String CodeFriend::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeFriend::to_string( String& result ) +{ + result.append_fmt( "friend %S", ast->Declaration->to_string() ); + + if ( ast->Declaration->Type != ECode::Function && result[result.length() - 1] != ';' ) + { + result.append( ";" ); + } + + if ( ast->InlineCmt ) + result.append_fmt( " %S", ast->InlineCmt->Content ); + else + result.append( "\n" ); +} + +String CodeFn::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Function : + to_string_def( result ); + break; + case Function_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeFn::to_string_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export" ); + + if ( ast->Attributes ) + result.append_fmt( " %S ", ast->Attributes.to_string() ); + + b32 prefix_specs = false; + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ! ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + + prefix_specs = true; + } + } + } + + if ( ast->Attributes || prefix_specs ) + result.append( "\n" ); + + if ( ast->ReturnType ) + result.append_fmt( "%S %S(", ast->ReturnType.to_string(), ast->Name ); + + else + result.append_fmt( "%S(", ast->Name ); + + if ( ast->Params ) + result.append_fmt( "%S)", ast->Params.to_string() ); + + else + result.append( ")" ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + result.append_fmt( "\n{\n%S\n}\n", ast->Body.to_string() ); +} + +void CodeFn::to_string_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + bool prefix_specs = false; + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ! ESpecifier::is_trailing( spec ) || ! ( spec != ESpecifier::Pure ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + + prefix_specs = true; + } + } + } + + if ( ast->Attributes || prefix_specs ) + { + result.append( "\n" ); + } + + if ( ast->ReturnType ) + result.append_fmt( "%S %S(", ast->ReturnType.to_string(), ast->Name ); + + else + result.append_fmt( "%S(", ast->Name ); + + if ( ast->Params ) + result.append_fmt( "%S)", ast->Params.to_string() ); + + else + result.append( ")" ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + if ( ast->Specs && ast->Specs.has( ESpecifier::Pure ) >= 0 ) + result.append( " = 0;" ); + else if ( ast->Body ) + result.append_fmt( " = %S;", ast->Body.to_string() ); + + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); +} + +String CodeModule::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeModule::to_string( String& result ) +{ + if ( ( ( u32( ModuleFlag::Export ) & u32( ast->ModuleFlags ) ) == u32( ModuleFlag::Export ) ) ) + result.append( "export " ); + + if ( ( ( u32( ModuleFlag::Import ) & u32( ast->ModuleFlags ) ) == u32( ModuleFlag::Import ) ) ) + result.append( "import " ); + + result.append_fmt( "%S;\n", ast->Name ); +} + +String CodeNS::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeNS::to_string( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append_fmt( "namespace %S\n{\n%S\n}\n", ast->Name, ast->Body.to_string() ); +} + +String CodeOperator::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Operator : + case Operator_Member : + to_string_def( result ); + break; + case Operator_Fwd : + case Operator_Member_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeOperator::to_string_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ! ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + if ( ast->Attributes || ast->Specs ) + { + result.append( "\n" ); + } + + if ( ast->ReturnType ) + result.append_fmt( "%S %S (", ast->ReturnType.to_string(), ast->Name ); + + if ( ast->Params ) + result.append_fmt( "%S)", ast->Params.to_string() ); + + else + result.append( ")" ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + result.append_fmt( "\n{\n%S\n}\n", ast->Body.to_string() ); +} + +void CodeOperator::to_string_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "%S\n", ast->Attributes.to_string() ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ! ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + if ( ast->Attributes || ast->Specs ) + { + result.append( "\n" ); + } + + result.append_fmt( "%S %S (", ast->ReturnType.to_string(), ast->Name ); + + if ( ast->Params ) + result.append_fmt( "%S)", ast->Params.to_string() ); + + else + result.append_fmt( ")" ); + + if ( ast->Specs ) + { + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + } + + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); +} + +String CodeOpCast::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Operator_Cast : + to_string_def( result ); + break; + case Operator_Cast_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeOpCast::to_string_def( String& result ) +{ + if ( ast->Specs ) + { + // TODO : Add support for specifies before the operator keyword + + if ( ast->Name && ast->Name.length() ) + result.append_fmt( "%Soperator %S()", ast->Name, ast->ValueType.to_string() ); + else + result.append_fmt( "operator %S()", ast->ValueType.to_string() ); + + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %.*s", spec_str.Len, spec_str.Ptr ); + } + } + + result.append_fmt( "\n{\n%S\n}\n", ast->Body.to_string() ); + return; + } + + if ( ast->Name && ast->Name.length() ) + result.append_fmt( "%Soperator %S()\n{\n%S\n}\n", ast->Name, ast->ValueType.to_string(), ast->Body.to_string() ); + else + result.append_fmt( "operator %S()\n{\n%S\n}\n", ast->ValueType.to_string(), ast->Body.to_string() ); +} + +void CodeOpCast::to_string_fwd( String& result ) +{ + if ( ast->Specs ) + { + // TODO : Add support for specifies before the operator keyword + + result.append_fmt( "operator %S()", ast->ValueType.to_string() ); + + for ( SpecifierT spec : ast->Specs ) + { + if ( ESpecifier::is_trailing( spec ) ) + { + StrC spec_str = ESpecifier::to_str( spec ); + result.append_fmt( " %*s", spec_str.Len, spec_str.Ptr ); + } + } + + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + return; + } + + if ( ast->InlineCmt ) + result.append_fmt( "operator %S(); %S", ast->ValueType.to_string() ); + else + result.append_fmt( "operator %S();\n", ast->ValueType.to_string() ); +} + +String CodeParam::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeParam::to_string( String& result ) +{ + if ( ast->Macro ) + { + // Related to parsing: ( , ... ) + result.append( ast->Macro.ast->Content ); + // Could also be: ( , ... ) + } + + if ( 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 if ( ast->ValueType ) + result.append_fmt( " %S", ast->ValueType.to_string() ); + + if ( ast->PostNameMacro ) + { + result.append_fmt(" %S", ast->PostNameMacro.to_string() ); + } + + if ( ast->Value ) + result.append_fmt( " = %S", ast->Value.to_string() ); + + if ( ast->NumEntries - 1 > 0 ) + { + for ( CodeParam param : ast->Next ) + { + result.append_fmt( ", %S", param.to_string() ); + } + } +} + +String CodePreprocessCond::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Preprocess_If : + to_string_if( result ); + break; + case Preprocess_IfDef : + to_string_ifdef( result ); + break; + case Preprocess_IfNotDef : + to_string_ifndef( result ); + break; + case Preprocess_ElIf : + to_string_elif( result ); + break; + case Preprocess_Else : + to_string_else( result ); + break; + case Preprocess_EndIf : + to_string_endif( result ); + break; + } + return result; +} + +void CodePreprocessCond::to_string_if( String& result ) +{ + result.append_fmt( "#if %S\n", ast->Content ); +} + +void CodePreprocessCond::to_string_ifdef( String& result ) +{ + result.append_fmt( "#ifdef %S\n", ast->Content ); +} + +void CodePreprocessCond::to_string_ifndef( String& result ) +{ + result.append_fmt( "#ifndef %S\n", ast->Content ); +} + +void CodePreprocessCond::to_string_elif( String& result ) +{ + result.append_fmt( "#elif %S\n", ast->Content ); +} + +void CodePreprocessCond::to_string_else( String& result ) +{ + result.append_fmt( "#else\n" ); +} + +void CodePreprocessCond::to_string_endif( String& result ) +{ + result.append_fmt( "#endif\n" ); +} + +String CodePragma::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodePragma::to_string( String& result ) +{ + result.append_fmt( "#pragma %S\n", ast->Content ); +} + +String CodeSpecifiers::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeSpecifiers::to_string( String& result ) +{ + s32 idx = 0; + s32 left = ast->NumEntries; + while ( left-- ) + { + StrC spec = ESpecifier::to_str( ast->ArrSpecs[idx] ); + result.append_fmt( "%.*s ", spec.Len, spec.Ptr ); + idx++; + } +} + +String CodeStruct::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Struct : + to_string_def( result ); + break; + case Struct_Fwd : + to_string_fwd( result ); + break; + } + return result; +} + +void CodeStruct::to_string_def( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append( "struct " ); + + if ( ast->Attributes ) + { + result.append_fmt( "%S ", ast->Attributes.to_string() ); + } + + if ( ast->ParentType ) + { + char const* access_level = to_str( ast->ParentAccess ); + + result.append_fmt( "%S : %s %S", ast->Name, access_level, ast->ParentType.to_string() ); + + CodeType interface = ast->ParentType->Next->cast(); + if ( interface ) + result.append( "\n" ); + + while ( interface ) + { + result.append_fmt( ", %S", interface.to_string() ); + interface = interface->Next ? interface->Next->cast() : CodeType { nullptr }; + } + } + else if ( ast->Name ) + { + result.append( ast->Name ); + } + + if ( ast->InlineCmt ) + { + result.append_fmt( " // %S", ast->InlineCmt->Content ); + } + + result.append_fmt( "\n{\n%S\n}", ast->Body.to_string() ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + result.append( ";\n" ); +} + +void CodeStruct::to_string_fwd( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "struct %S %S", ast->Attributes.to_string(), ast->Name ); + + else + result.append_fmt( "struct %S", ast->Name ); + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + { + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + } +} + +String CodeTemplate::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeTemplate::to_string( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Params ) + result.append_fmt( "template< %S >\n%S", ast->Params.to_string(), ast->Declaration.to_string() ); + else + result.append_fmt( "template<>\n%S", ast->Declaration.to_string() ); +} + +String CodeTypedef::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeTypedef::to_string( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append( "typedef " ); + + // Determines if the typedef is a function typename + if ( ast->UnderlyingType->ReturnType ) + result.append( ast->UnderlyingType.to_string() ); + else + result.append_fmt( "%S %S", ast->UnderlyingType.to_string(), ast->Name ); + + if ( ast->UnderlyingType->Type == ECode::Typename && ast->UnderlyingType->ArrExpr ) + { + result.append_fmt( "[ %S ];", ast->UnderlyingType->ArrExpr->to_string() ); + + AST* next_arr_expr = ast->UnderlyingType->ArrExpr->Next; + while ( next_arr_expr ) + { + result.append_fmt( "[ %S ];", next_arr_expr->to_string() ); + next_arr_expr = next_arr_expr->Next; + } + } + else + { + result.append( ";" ); + } + + if ( ast->InlineCmt ) + result.append_fmt( " %S", ast->InlineCmt->Content ); + else + result.append( "\n" ); +} + +String CodeType::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeType::to_string( String& result ) +{ +#if defined(GEN_USE_NEW_TYPENAME_PARSING) + if ( ast->ReturnType && ast->Params ) + { + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + else + { + if ( ast->Specs ) + result.append_fmt( "%S ( %S ) ( %S ) %S", ast->ReturnType.to_string(), ast->Name, ast->Params.to_string(), ast->Specs.to_string() ); + else + result.append_fmt( "%S ( %S ) ( %S )", ast->ReturnType.to_string(), ast->Name, ast->Params.to_string() ); + } + + break; + } +#else + if ( ast->ReturnType && ast->Params ) + { + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + else + { + if ( ast->Specs ) + result.append_fmt( "%S %S ( %S ) %S", ast->ReturnType.to_string(), ast->Name, ast->Params.to_string(), ast->Specs.to_string() ); + else + result.append_fmt( "%S %S ( %S )", ast->ReturnType.to_string(), ast->Name, ast->Params.to_string() ); + } + + return; + } +#endif + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->Specs ) + result.append_fmt( "%S %S", ast->Name, ast->Specs.to_string() ); + else + result.append_fmt( "%S", ast->Name ); + + if ( ast->IsParamPack ) + result.append( "..." ); +} + +String CodeUnion::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeUnion::to_string( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + result.append( "union " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->Name ) + { + result.append_fmt( "%S\n{\n%S\n}", ast->Name, ast->Body.to_string() ); + } + else + { + // Anonymous union + result.append_fmt( "\n{\n%S\n}", ast->Body.to_string() ); + } + + if ( ast->Parent.ast == nullptr || ( ast->Parent->Type != ECode::Typedef && ast->Parent->Type != ECode::Variable ) ) + result.append( ";\n" ); +} + +String CodeUsing::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + switch ( ast->Type ) + { + using namespace ECode; + case Using : + to_string( result ); + break; + case Using_Namespace : + to_string_ns( result ); + break; + } + return result; +} + +void CodeUsing::to_string( String& result ) +{ + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Attributes.to_string() ); + + if ( ast->UnderlyingType ) + { + result.append_fmt( "using %S = %S", ast->Name, ast->UnderlyingType.to_string() ); + + if ( ast->UnderlyingType->ArrExpr ) + { + result.append_fmt( "[ %S ]", ast->UnderlyingType->ArrExpr.to_string() ); + + AST* next_arr_expr = ast->UnderlyingType->ArrExpr->Next; + while ( next_arr_expr ) + { + result.append_fmt( "[ %S ]", next_arr_expr->to_string() ); + next_arr_expr = next_arr_expr->Next; + } + } + + result.append( ";" ); + } + else + result.append_fmt( "using %S;", ast->Name ); + + if ( ast->InlineCmt ) + result.append_fmt( " %S\n", ast->InlineCmt->Content ); + else + result.append( "\n" ); +} + +void CodeUsing::to_string_ns( String& result ) +{ + if ( ast->InlineCmt ) + result.append_fmt( "using namespace $S; %S", ast->Name, ast->InlineCmt->Content ); + else + result.append_fmt( "using namespace %s;\n", ast->Name ); +} + +String CodeVar::to_string() +{ + String result = String::make( GlobalAllocator, "" ); + to_string( result ); + return result; +} + +void CodeVar::to_string( String& result ) +{ + if ( ast->Parent && ast->Parent->Type == ECode::Variable ) + { + // Its a comma-separated variable ( a NextVar ) + + if ( ast->Specs ) + result.append_fmt( "%S ", ast->Specs.to_string() ); + + result.append( ast->Name ); + + if ( ast->ValueType->ArrExpr ) + { + result.append_fmt( "[ %S ]", ast->ValueType->ArrExpr.to_string() ); + + AST* next_arr_expr = ast->ValueType->ArrExpr->Next; + while ( next_arr_expr ) + { + result.append_fmt( "[ %S ]", next_arr_expr->to_string() ); + next_arr_expr = next_arr_expr->Next; + } + } + + if ( ast->Value ) + result.append_fmt( " = %S", ast->Value.to_string() ); + + // Keep the chain going... + if ( ast->NextVar ) + result.append_fmt( ", %S", ast->NextVar.to_string() ); + + return; + } + + if ( bitfield_is_equal( u32, ast->ModuleFlags, ModuleFlag::Export ) ) + result.append( "export " ); + + if ( ast->Attributes || ast->Specs ) + { + if ( ast->Attributes ) + result.append_fmt( "%S ", ast->Specs.to_string() ); + + if ( ast->Specs ) + result.append_fmt( "%S\n", ast->Specs.to_string() ); + + result.append_fmt( "%S %S", ast->ValueType.to_string(), ast->Name ); + + if ( ast->ValueType->ArrExpr ) + { + result.append_fmt( "[ %S ]", ast->ValueType->ArrExpr.to_string() ); + + AST* next_arr_expr = ast->ValueType->ArrExpr->Next; + while ( next_arr_expr ) + { + result.append_fmt( "[ %S ]", next_arr_expr->to_string() ); + next_arr_expr = next_arr_expr->Next; + } + } + + if ( ast->BitfieldSize ) + result.append_fmt( " : %S", ast->BitfieldSize.to_string() ); + + if ( ast->Value ) + result.append_fmt( " = %S", ast->Value.to_string() ); + + if ( ast->NextVar ) + result.append_fmt( ", %S", ast->NextVar.to_string() ); + + if ( ast->InlineCmt ) + result.append_fmt( "; %S", ast->InlineCmt->Content ); + else + result.append( ";\n" ); + + return; + } + + if ( ast->BitfieldSize ) + result.append_fmt( "%S %S : %S", ast->ValueType.to_string(), ast->Name, ast->BitfieldSize.to_string() ); + + else if ( ast->ValueType->ArrExpr ) + { + result.append_fmt( "%S %S[ %S ]", ast->ValueType.to_string(), ast->Name, ast->ValueType->ArrExpr.to_string() ); + + AST* next_arr_expr = ast->ValueType->ArrExpr->Next; + while ( next_arr_expr ) + { + result.append_fmt( "[ %S ]", next_arr_expr->to_string() ); + next_arr_expr = next_arr_expr->Next; + } + } + + else + result.append_fmt( "%S %S", ast->ValueType.to_string(), ast->Name ); + + if ( ast->Value ) + result.append_fmt( " = %S", ast->Value.to_string() ); + + if ( ast->NextVar ) + result.append_fmt( ", %S", ast->NextVar.to_string() ); + + result.append( ";" ); + + if ( ast->InlineCmt ) + result.append_fmt( " %S", ast->InlineCmt->Content ); + else + result.append( "\n" ); +} + +#pragma endregion AST + +#pragma region Interface + +namespace parser +{ + internal void init(); + internal void deinit(); +} + +internal void* Global_Allocator_Proc( void* allocator_data, AllocType type, sw size, sw alignment, void* old_memory, sw old_size, u64 flags ) +{ + Arena* last = &Global_AllocatorBuckets.back(); + + switch ( type ) + { + case EAllocation_ALLOC : + { + if ( ( last->TotalUsed + size ) > last->TotalSize ) + { + Arena bucket = Arena::init_from_allocator( heap(), Global_BucketSize ); + + if ( bucket.PhysicalStart == nullptr ) + GEN_FATAL( "Failed to create bucket for Global_AllocatorBuckets" ); + + if ( ! Global_AllocatorBuckets.append( bucket ) ) + GEN_FATAL( "Failed to append bucket to Global_AllocatorBuckets" ); + + last = &Global_AllocatorBuckets.back(); + } + + return alloc_align( *last, size, alignment ); + } + case EAllocation_FREE : + { + // Doesn't recycle. + } + break; + case EAllocation_FREE_ALL : + { + // Memory::cleanup instead. + } + break; + case EAllocation_RESIZE : + { + if ( last->TotalUsed + size > last->TotalSize ) + { + Arena bucket = Arena::init_from_allocator( heap(), Global_BucketSize ); + + if ( bucket.PhysicalStart == nullptr ) + GEN_FATAL( "Failed to create bucket for Global_AllocatorBuckets" ); + + if ( ! Global_AllocatorBuckets.append( bucket ) ) + GEN_FATAL( "Failed to append bucket to Global_AllocatorBuckets" ); + + last = &Global_AllocatorBuckets.back(); + } + + void* result = alloc_align( last->Backing, size, alignment ); + + if ( result != nullptr && old_memory != nullptr ) + { + mem_copy( result, old_memory, old_size ); + } + + return result; + } + } + + return nullptr; +} + +internal void define_constants() +{ + Code::Global = make_code(); + Code::Global->Name = get_cached_string( txt( "Global Code" ) ); + Code::Global->Content = Code::Global->Name; + + Code::Invalid = make_code(); + Code::Invalid.set_global(); + + t_empty = (CodeType)make_code(); + t_empty->Type = ECode::Typename; + t_empty->Name = get_cached_string( txt( "" ) ); + t_empty.set_global(); + + access_private = make_code(); + access_private->Type = ECode::Access_Private; + access_private->Name = get_cached_string( txt( "private:\n" ) ); + access_private.set_global(); + + access_protected = make_code(); + access_protected->Type = ECode::Access_Protected; + access_protected->Name = get_cached_string( txt( "protected:\n" ) ); + access_protected.set_global(); + + access_public = make_code(); + access_public->Type = ECode::Access_Public; + access_public->Name = get_cached_string( txt( "public:\n" ) ); + access_public.set_global(); + + attrib_api_export = def_attributes( code( GEN_API_Export_Code ) ); + attrib_api_export.set_global(); + + attrib_api_import = def_attributes( code( GEN_API_Import_Code ) ); + attrib_api_import.set_global(); + + module_global_fragment = make_code(); + module_global_fragment->Type = ECode::Untyped; + module_global_fragment->Name = get_cached_string( txt( "module;" ) ); + module_global_fragment->Content = module_global_fragment->Name; + module_global_fragment.set_global(); + + module_private_fragment = make_code(); + module_private_fragment->Type = ECode::Untyped; + module_private_fragment->Name = get_cached_string( txt( "module : private;" ) ); + module_private_fragment->Content = module_private_fragment->Name; + module_private_fragment.set_global(); + + fmt_newline = make_code(); + fmt_newline->Type = ECode::NewLine; + fmt_newline.set_global(); + + pragma_once = (CodePragma)make_code(); + pragma_once->Type = ECode::Preprocess_Pragma; + pragma_once->Name = get_cached_string( txt( "once" ) ); + pragma_once->Content = pragma_once->Name; + pragma_once.set_global(); + + param_varadic = (CodeType)make_code(); + param_varadic->Type = ECode::Parameters; + param_varadic->Name = get_cached_string( txt( "..." ) ); + param_varadic->ValueType = t_empty; + param_varadic.set_global(); + + preprocess_else = (CodePreprocessCond)make_code(); + preprocess_else->Type = ECode::Preprocess_Else; + preprocess_else.set_global(); + + preprocess_endif = (CodePreprocessCond)make_code(); + preprocess_endif->Type = ECode::Preprocess_EndIf; + preprocess_endif.set_global(); + +#define def_constant_code_type( Type_ ) \ + t_##Type_ = def_type( name( Type_ ) ); \ + t_##Type_.set_global(); + + def_constant_code_type( auto ); + def_constant_code_type( void ); + def_constant_code_type( int ); + def_constant_code_type( bool ); + def_constant_code_type( char ); + def_constant_code_type( wchar_t ); + def_constant_code_type( class ); + def_constant_code_type( typename ); + +#ifdef GEN_DEFINE_LIBRARY_CODE_CONSTANTS + t_b32 = def_type( name( b32 ) ); + + def_constant_code_type( s8 ); + def_constant_code_type( s16 ); + def_constant_code_type( s32 ); + def_constant_code_type( s64 ); + + def_constant_code_type( u8 ); + def_constant_code_type( u16 ); + def_constant_code_type( u32 ); + def_constant_code_type( u64 ); + + def_constant_code_type( sw ); + def_constant_code_type( uw ); + + def_constant_code_type( f32 ); + def_constant_code_type( f64 ); +#endif +#undef def_constant_code_type + + +#define def_constant_spec( Type_, ... ) \ + spec_##Type_ = def_specifiers( num_args( __VA_ARGS__ ), __VA_ARGS__ ); \ + spec_##Type_.set_global(); + +#pragma push_macro( "FORCEINLINE" ) +#pragma push_macro( "global" ) +#pragma push_macro( "internal" ) +#pragma push_macro( "local_persist" ) +#pragma push_macro( "neverinline" ) +#undef FORCEINLINE +#undef global +#undef internal +#undef local_persist +#undef neverinline + def_constant_spec( const, ESpecifier::Const ); + def_constant_spec( consteval, ESpecifier::Consteval ); + def_constant_spec( constexpr, ESpecifier::Constexpr ); + def_constant_spec( constinit, ESpecifier::Constinit ); + def_constant_spec( extern_linkage, ESpecifier::External_Linkage ); + def_constant_spec( final, ESpecifier::Final ); + def_constant_spec( FORCEINLINE, ESpecifier::ForceInline ); + def_constant_spec( global, ESpecifier::Global ); + def_constant_spec( inline, ESpecifier::Inline ); + def_constant_spec( internal_linkage, ESpecifier::Internal_Linkage ); + def_constant_spec( local_persist, ESpecifier::Local_Persist ); + def_constant_spec( mutable, ESpecifier::Mutable ); + def_constant_spec( neverinline, ESpecifier::NeverInline ); + def_constant_spec( noexcept, ESpecifier::NoExceptions ); + def_constant_spec( override, ESpecifier::Override ); + def_constant_spec( ptr, ESpecifier::Ptr ); + def_constant_spec( pure, ESpecifier::Pure ) def_constant_spec( ref, ESpecifier::Ref ); + def_constant_spec( register, ESpecifier::Register ); + def_constant_spec( rvalue, ESpecifier::RValue ); + def_constant_spec( static_member, ESpecifier::Static ); + def_constant_spec( thread_local, ESpecifier::Thread_Local ); + def_constant_spec( virtual, ESpecifier::Virtual ); + def_constant_spec( volatile, ESpecifier::Volatile ) + + spec_local_persist = def_specifiers( 1, ESpecifier::Local_Persist ); + spec_local_persist.set_global(); + +#pragma pop_macro( "FORCEINLINE" ) +#pragma pop_macro( "global" ) +#pragma pop_macro( "internal" ) +#pragma pop_macro( "local_persist" ) +#pragma pop_macro( "neverinline" ) + +#undef def_constant_spec +} + +void init() +{ + // Setup global allocator + { + GlobalAllocator = AllocatorInfo { &Global_Allocator_Proc, nullptr }; + + Global_AllocatorBuckets = Array::init_reserve( heap(), 128 ); + + if ( Global_AllocatorBuckets == nullptr ) + GEN_FATAL( "Failed to reserve memory for Global_AllocatorBuckets" ); + + Arena bucket = Arena::init_from_allocator( heap(), Global_BucketSize ); + + if ( bucket.PhysicalStart == nullptr ) + GEN_FATAL( "Failed to create first bucket for Global_AllocatorBuckets" ); + + Global_AllocatorBuckets.append( bucket ); + } + + // Setup the arrays + { + CodePools = Array::init_reserve( Allocator_DataArrays, InitSize_DataArrays ); + + if ( CodePools == nullptr ) + GEN_FATAL( "gen::init: Failed to initialize the CodePools array" ); + + StringArenas = Array::init_reserve( Allocator_DataArrays, InitSize_DataArrays ); + + if ( StringArenas == nullptr ) + GEN_FATAL( "gen::init: Failed to initialize the StringArenas array" ); + } + + // Setup the code pool and code entries arena. + { + Pool code_pool = Pool::init( Allocator_CodePool, CodePool_NumBlocks, sizeof( AST ) ); + + if ( code_pool.PhysicalStart == nullptr ) + GEN_FATAL( "gen::init: Failed to initialize the code pool" ); + + CodePools.append( code_pool ); + + LexArena = Arena::init_from_allocator( Allocator_Lexer, LexAllocator_Size ); + + Arena string_arena = Arena::init_from_allocator( Allocator_StringArena, SizePer_StringArena ); + + if ( string_arena.PhysicalStart == nullptr ) + GEN_FATAL( "gen::init: Failed to initialize the string arena" ); + + StringArenas.append( string_arena ); + } + + // Setup the hash tables + { + StringCache = StringTable::init( Allocator_StringTable ); + + if ( StringCache.Entries == nullptr ) + GEN_FATAL( "gen::init: Failed to initialize the StringCache" ); + } + + // Preprocessor Defines + PreprocessorDefines = Array::init_reserve( GlobalAllocator, kilobytes( 1 ) ); + + define_constants(); + parser::init(); +} + +void deinit() +{ + uw index = 0; + uw left = CodePools.num(); + do + { + Pool* code_pool = &CodePools[index]; + code_pool->free(); + index++; + } while ( left--, left ); + + index = 0; + left = StringArenas.num(); + do + { + Arena* string_arena = &StringArenas[index]; + string_arena->free(); + index++; + } while ( left--, left ); + + StringCache.destroy(); + + CodePools.free(); + StringArenas.free(); + + LexArena.free(); + + PreprocessorDefines.free(); + + index = 0; + left = Global_AllocatorBuckets.num(); + do + { + Arena* bucket = &Global_AllocatorBuckets[index]; + bucket->free(); + index++; + } while ( left--, left ); + + Global_AllocatorBuckets.free(); + parser::deinit(); +} + +void reset() +{ + s32 index = 0; + s32 left = CodePools.num(); + do + { + Pool* code_pool = &CodePools[index]; + code_pool->clear(); + index++; + } while ( left--, left ); + + index = 0; + left = StringArenas.num(); + do + { + Arena* string_arena = &StringArenas[index]; + string_arena->TotalUsed = 0; + ; + index++; + } while ( left--, left ); + + StringCache.clear(); + + define_constants(); +} + +AllocatorInfo get_string_allocator( s32 str_length ) +{ + Arena* last = &StringArenas.back(); + + uw size_req = str_length + sizeof( String::Header ) + sizeof( char* ); + + if ( last->TotalUsed + sw(size_req) > last->TotalSize ) + { + Arena new_arena = Arena::init_from_allocator( Allocator_StringArena, SizePer_StringArena ); + + if ( ! StringArenas.append( new_arena ) ) + GEN_FATAL( "gen::get_string_allocator: Failed to allocate a new string arena" ); + + last = &StringArenas.back(); + } + + return *last; +} + +// Will either make or retrive a code string. +StringCached get_cached_string( StrC str ) +{ + s32 hash_length = str.Len > kilobytes( 1 ) ? kilobytes( 1 ) : str.Len; + u64 key = crc32( str.Ptr, hash_length ); + { + StringCached* result = StringCache.get( key ); + + if ( result ) + return *result; + } + + String result = String::make( get_string_allocator( str.Len ), str ); + StringCache.set( key, result ); + + return result; +} + +// Used internally to retireve a Code object form the CodePool. +Code make_code() +{ + Pool* allocator = &CodePools.back(); + if ( allocator->FreeList == nullptr ) + { + Pool code_pool = Pool::init( Allocator_CodePool, CodePool_NumBlocks, sizeof( AST ) ); + + if ( code_pool.PhysicalStart == nullptr ) + GEN_FATAL( "gen::make_code: Failed to allocate a new code pool - CodePool allcoator returned nullptr." ); + + if ( ! CodePools.append( code_pool ) ) + GEN_FATAL( "gen::make_code: Failed to allocate a new code pool - CodePools failed to append new pool." ); + + allocator = &CodePools.back(); + } + + Code result { rcast( AST*, alloc( *allocator, sizeof( AST ) ) ) }; + mem_set( result.ast, 0, sizeof( AST ) ); + // result->Type = ECode::Invalid; + + // result->Content = { nullptr }; + // result->Prev = { nullptr }; + // result->Next = { nullptr }; + // result->Token = nullptr; + // result->Parent = { nullptr }; + // result->Name = { nullptr }; + // result->Type = ECode::Invalid; + // result->ModuleFlags = ModuleFlag::Invalid; + // result->NumEntries = 0; + + return result; +} + +void set_allocator_data_arrays( AllocatorInfo allocator ) +{ + Allocator_DataArrays = allocator; +} + +void set_allocator_code_pool( AllocatorInfo allocator ) +{ + Allocator_CodePool = allocator; +} + +void set_allocator_lexer( AllocatorInfo allocator ) +{ + Allocator_Lexer = allocator; +} + +void set_allocator_string_arena( AllocatorInfo allocator ) +{ + Allocator_StringArena = allocator; +} + +void set_allocator_string_table( AllocatorInfo allocator ) +{ + Allocator_StringArena = allocator; +} + +#pragma region Upfront + +enum class OpValidateResult : u32 +{ + Fail, + Global, + Member +}; + +OpValidateResult operator__validate( OperatorT op, CodeParam params_code, CodeType ret_type, CodeSpecifiers specifier ) +{ + using namespace EOperator; + + if ( op == EOperator::Invalid ) + { + log_failure( "gen::def_operator: op cannot be invalid" ); + return OpValidateResult::Fail; + } + +#pragma region Helper Macros +#define check_params() \ + if ( ! params_code ) \ + { \ + log_failure( "gen::def_operator: params is null and operator%s requires it", to_str( op ) ); \ + return OpValidateResult::Fail; \ + } \ + if ( params_code->Type != ECode::Parameters ) \ + { \ + log_failure( "gen::def_operator: params is not of Parameters type - %s", params_code.debug_str() ); \ + return OpValidateResult::Fail; \ + } + +#define check_param_eq_ret() \ + if ( ! is_member_symbol && ! params_code->ValueType.is_equal( ret_type ) ) \ + { \ + log_failure( \ + "gen::def_operator: operator%s requires first parameter to equal return type\n" \ + "param types: %s\n" \ + "return type: %s", \ + to_str( op ).Ptr, \ + params_code.debug_str(), \ + ret_type.debug_str() \ + ); \ + return OpValidateResult::Fail; \ + } +#pragma endregion Helper Macros + + if ( ! ret_type ) + { + log_failure( "gen::def_operator: ret_type is null but is required by operator%s", to_str( op ) ); + } + + if ( ret_type->Type != ECode::Typename ) + { + log_failure( "gen::def_operator: ret_type is not of typename type - %s", ret_type.debug_str() ); + return OpValidateResult::Fail; + } + + bool is_member_symbol = false; + + switch ( op ) + { +#define specs( ... ) num_args( __VA_ARGS__ ), __VA_ARGS__ + case Assign : + check_params(); + + if ( params_code->NumEntries > 1 ) + { + log_failure( + "gen::def_operator: " + "operator%s does not support non-member definition (more than one parameter provided) - %s", + to_str( op ), + params_code.debug_str() + ); + return OpValidateResult::Fail; + } + + is_member_symbol = true; + break; + + case Assign_Add : + case Assign_Subtract : + case Assign_Multiply : + case Assign_Divide : + case Assign_Modulo : + case Assign_BAnd : + case Assign_BOr : + case Assign_BXOr : + case Assign_LShift : + case Assign_RShift : + check_params(); + + if ( params_code->NumEntries == 1 ) + is_member_symbol = true; + + else + check_param_eq_ret(); + + if ( params_code->NumEntries > 2 ) + { + log_failure( + "gen::def_operator: operator%s may not be defined with more than two parametes - param count; %d\n%s", + to_str( op ), + params_code->NumEntries, + params_code.debug_str() + ); + return OpValidateResult::Fail; + } + break; + + case Increment : + case Decrement : + // If its not set, it just means its a prefix member op. + if ( params_code ) + { + if ( params_code->Type != ECode::Parameters ) + { + log_failure( "gen::def_operator: operator%s params code provided is not of Parameters type - %s", to_str( op ), params_code.debug_str() ); + return OpValidateResult::Fail; + } + + switch ( params_code->NumEntries ) + { + case 1 : + if ( params_code->ValueType.is_equal( t_int ) ) + is_member_symbol = true; + + else + check_param_eq_ret(); + break; + + case 2 : + check_param_eq_ret(); + + if ( ! params_code.get( 1 ).is_equal( t_int ) ) + { + log_failure( + "gen::def_operator: " + "operator%s requires second parameter of non-member definition to be int for post-decrement", + to_str( op ) + ); + return OpValidateResult::Fail; + } + break; + + default : + log_failure( + "gen::def_operator: operator%s recieved unexpected number of parameters recived %d instead of 0-2", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + } + break; + + case Unary_Plus : + case Unary_Minus : + if ( ! params_code ) + is_member_symbol = true; + + else + { + if ( params_code->Type != ECode::Parameters ) + { + log_failure( "gen::def_operator: params is not of Parameters type - %s", params_code.debug_str() ); + return OpValidateResult::Fail; + } + + if ( params_code->ValueType.is_equal( ret_type ) ) + { + log_failure( + "gen::def_operator: " + "operator%s is non-member symbol yet first paramter does not equal return type\n" + "param type: %s\n" + "return type: %s\n", + params_code.debug_str(), + ret_type.debug_str() + ); + return OpValidateResult::Fail; + } + + if ( params_code->NumEntries > 1 ) + { + log_failure( + "gen::def_operator: operator%s may not have more than one parameter - param count: %d", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + } + break; + + case BNot : + { + // Some compilers let you do this... +#if 0 + if ( ! ret_type.is_equal( t_bool) ) + { + log_failure( "gen::def_operator: return type is not a boolean - %s", params_code.debug_str() ); + return OpValidateResult::Fail; + } +#endif + + if ( ! params_code ) + is_member_symbol = true; + + else + { + if ( params_code->Type != ECode::Parameters ) + { + log_failure( "gen::def_operator: params is not of Parameters type - %s", params_code.debug_str() ); + return OpValidateResult::Fail; + } + + if ( params_code->NumEntries > 1 ) + { + log_failure( + "gen::def_operator: operator%s may not have more than one parameter - param count: %d", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + } + break; + } + + case Add : + case Subtract : + case Multiply : + case Divide : + case Modulo : + case BAnd : + case BOr : + case BXOr : + case LShift : + case RShift : + check_params(); + + switch ( params_code->NumEntries ) + { + case 1 : + is_member_symbol = true; + break; + + case 2 : + if ( ! params_code->ValueType.is_equal( ret_type ) ) + { + log_failure( + "gen::def_operator: " + "operator%s is non-member symbol yet first paramter does not equal return type\n" + "param type: %s\n" + "return type: %s\n", + params_code.debug_str(), + ret_type.debug_str() + ); + return OpValidateResult::Fail; + } + break; + + default : + log_failure( + "gen::def_operator: operator%s recieved unexpected number of paramters recived %d instead of 0-2", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + break; + + case UnaryNot : + if ( ! params_code ) + is_member_symbol = true; + + else + { + if ( params_code->Type != ECode::Parameters ) + { + log_failure( "gen::def_operator: params is not of Parameters type - %s", params_code.debug_str() ); + return OpValidateResult::Fail; + } + + if ( params_code->NumEntries != 1 ) + { + log_failure( + "gen::def_operator: operator%s recieved unexpected number of paramters recived %d instead of 0-1", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + } + + if ( ! ret_type.is_equal( t_bool ) ) + { + log_failure( "gen::def_operator: operator%s return type must be of type bool - %s", to_str( op ), ret_type.debug_str() ); + return OpValidateResult::Fail; + } + break; + + case LAnd : + case LOr : + case LEqual : + case LNot : + case Lesser : + case Greater : + case LesserEqual : + case GreaterEqual : + check_params(); + + switch ( params_code->NumEntries ) + { + case 1 : + is_member_symbol = true; + break; + + case 2 : + break; + + default : + log_failure( + "gen::def_operator: operator%s recieved unexpected number of paramters recived %d instead of 1-2", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + break; + + case Indirection : + case AddressOf : + case MemberOfPointer : + if ( params_code && params_code->NumEntries > 1 ) + { + log_failure( + "gen::def_operator: operator%s recieved unexpected number of paramters recived %d instead of 0-1", + to_str( op ), + params_code->NumEntries + ); + return OpValidateResult::Fail; + } + else + { + is_member_symbol = true; + } + break; + + case PtrToMemOfPtr : + if ( params_code ) + { + log_failure( "gen::def_operator: operator%s expects no paramters - %s", to_str( op ), params_code.debug_str() ); + return OpValidateResult::Fail; + } + break; + + case Subscript : + case FunctionCall : + case Comma : + check_params(); + break; + + case New : + case Delete : + // This library doesn't support validating new and delete yet. + break; +#undef specs + } + + return is_member_symbol ? OpValidateResult::Member : OpValidateResult::Global; +#undef check_params +#undef check_ret_type +#undef check_param_eq_ret +} + +#pragma region Helper Marcos +// This snippet is used in nearly all the functions. +#define name_check( Context_, Name_ ) \ + { \ + if ( Name_.Len <= 0 ) \ + { \ + log_failure( "gen::" stringize( Context_ ) ": Invalid name length provided - %d", Name_.Len ); \ + return CodeInvalid; \ + } \ + \ + if ( Name_.Ptr == nullptr ) \ + { \ + log_failure( "gen::" stringize( Context_ ) ": name is null" ); \ + return CodeInvalid; \ + } \ + } + +#define null_check( Context_, Code_ ) \ + if ( ! Code_ ) \ + { \ + log_failure( "gen::" stringize( Context_ ) ": " stringize( Code_ ) " provided is null" ); \ + return CodeInvalid; \ + } + +#define null_or_invalid_check( Context_, Code_ ) \ + { \ + if ( ! Code_ ) \ + { \ + log_failure( "gen::" stringize( Context_ ) ": " stringize( Code_ ) " provided is null" ); \ + return CodeInvalid; \ + } \ + \ + if ( Code_->is_invalid() ) \ + { \ + log_failure( "gen::" stringize( Context_ ) ": " stringize( Code_ ) " provided is invalid" ); \ + return CodeInvalid; \ + } \ + } + +#define not_implemented( Context_ ) \ + log_failure( "gen::%s: This function is not implemented" ); \ + return CodeInvalid; +#pragma endregion Helper Marcos + +/* +The implementaiton of the upfront constructors involves doing three things: +* Validate the arguments given to construct the intended type of AST is valid. +* Construct said AST type. +* Lock the AST (set to readonly) and return the valid object. + +If any of the validation fails, it triggers a call to log_failure with as much info the give the user so that they can hopefully +identify the issue without having to debug too much (at least they can debug though...) + +The largest of the functions is related to operator overload definitions. +The library validates a good protion of their form and thus the argument processing for is quite a bit. +*/ +CodeAttributes def_attributes( StrC content ) +{ + if ( content.Len <= 0 || content.Ptr == nullptr ) + { + log_failure( "gen::def_attributes: Invalid attributes provided" ); + return CodeInvalid; + } + + Code result = make_code(); + result->Type = ECode::PlatformAttributes; + result->Name = get_cached_string( content ); + result->Content = result->Name; + + return (CodeAttributes)result; +} + +CodeComment def_comment( StrC content ) +{ + if ( content.Len <= 0 || content.Ptr == nullptr ) + { + log_failure( "gen::def_comment: Invalid comment provided:" ); + return CodeInvalid; + } + + static char line[MaxCommentLineLength]; + + String cmt_formatted = String::make_reserve( GlobalAllocator, kilobytes( 1 ) ); + char const* end = content.Ptr + content.Len; + char const* scanner = content.Ptr; + s32 curr = 0; + do + { + char const* next = scanner; + s32 length = 0; + while ( next != end && scanner[length] != '\n' ) + { + next = scanner + length; + length++; + } + length++; + + str_copy( line, scanner, length ); + cmt_formatted.append_fmt( "//%.*s", length, line ); + mem_set( line, 0, MaxCommentLineLength ); + + scanner += length; + } while ( scanner <= end ); + + if ( cmt_formatted.back() != '\n' ) + cmt_formatted.append( "\n" ); + + Code result = make_code(); + result->Type = ECode::Comment; + result->Name = get_cached_string( cmt_formatted ); + result->Content = result->Name; + + cmt_formatted.free(); + + return (CodeComment)result; +} + +CodeConstructor def_constructor( CodeParam params, Code initializer_list, Code body ) +{ + using namespace ECode; + + if ( params && params->Type != Parameters ) + { + log_failure( "gen::def_constructor: params must be of Parameters type - %s", params.debug_str() ); + return CodeInvalid; + } + + CodeConstructor result = (CodeConstructor)make_code(); + + if ( params ) + { + result->Params = params; + } + + if ( initializer_list ) + { + result->InitializerList = initializer_list; + } + + if ( body ) + { + switch ( body->Type ) + { + case Function_Body : + case Untyped : + break; + + default : + log_failure( "gen::def_constructor: body must be either of Function_Body or Untyped type - %s", body.debug_str() ); + return CodeInvalid; + } + + result->Type = Constructor; + result->Body = body; + } + else + { + result->Type = Constructor_Fwd; + } + + return result; +} + +CodeClass def_class( + StrC name, + Code body, + CodeType parent, + AccessSpec parent_access, + CodeAttributes attributes, + ModuleFlag mflags, + CodeType* interfaces, + s32 num_interfaces +) +{ + using namespace ECode; + + name_check( def_class, name ); + + if ( attributes && attributes->Type != PlatformAttributes ) + { + log_failure( "gen::def_class: attributes was not a 'PlatformAttributes' type: %s", attributes.debug_str() ); + return CodeInvalid; + } + + if ( parent && ( parent->Type != Class && parent->Type != Struct && parent->Type != Typename && parent->Type != Untyped ) ) + { + log_failure( "gen::def_class: parent provided is not type 'Class', 'Struct', 'Typeanme', or 'Untyped': %s", parent.debug_str() ); + return CodeInvalid; + } + + CodeClass result = (CodeClass)make_code(); + result->Name = get_cached_string( name ); + result->ModuleFlags = mflags; + + if ( body ) + { + switch ( body->Type ) + { + case Class_Body : + case Untyped : + break; + + default : + log_failure( "gen::def_class: body must be either of Class_Body or Untyped type - %s", body.debug_str() ); + return CodeInvalid; + } + + result->Type = Class; + result->Body = body; + result->Body->Parent = result; // TODO(Ed): Review this? + } + else + { + result->Type = Class_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( parent ) + { + result->ParentAccess = parent_access; + result->ParentType = parent; + } + + if ( interfaces ) + { + for ( s32 idx = 0; idx < num_interfaces; idx++ ) + { + result.add_interface( interfaces[idx] ); + } + } + + return result; +} + +CodeDefine def_define( StrC name, StrC content ) +{ + using namespace ECode; + + name_check( def_define, name ); + + // Defines can be empty definitions +#if 0 + if ( content.Len <= 0 || content.Ptr == nullptr ) + { + log_failure( "gen::def_define: Invalid value provided" ); + return CodeInvalid; + } +#endif + + CodeDefine result = (CodeDefine)make_code(); + result->Type = Preprocess_Define; + result->Name = get_cached_string( name ); + if ( content.Len <= 0 || content.Ptr == nullptr ) + { + result->Content = get_cached_string( txt( "" ) ); + } + else + result->Content = get_cached_string( content ); + + return result; +} + +CodeDestructor def_destructor( Code body, CodeSpecifiers specifiers ) +{ + using namespace ECode; + + if ( specifiers && specifiers->Type != Specifiers ) + { + log_failure( "gen::def_destructor: specifiers was not a 'Specifiers' type: %s", specifiers.debug_str() ); + return CodeInvalid; + } + + CodeDestructor result = (CodeDestructor)make_code(); + + if ( specifiers ) + result->Specs = specifiers; + + if ( body ) + { + switch ( body->Type ) + { + case Function_Body : + case Untyped : + break; + + default : + log_failure( "gen::def_destructor: body must be either of Function_Body or Untyped type - %s", body.debug_str() ); + return CodeInvalid; + } + + result->Type = Destructor; + result->Body = body; + } + else + { + result->Type = Destructor_Fwd; + } + + return result; +} + +CodeEnum def_enum( StrC name, Code body, CodeType type, EnumT specifier, CodeAttributes attributes, ModuleFlag mflags ) +{ + using namespace ECode; + + name_check( def_enum, name ); + + if ( type && type->Type != Typename ) + { + log_failure( "gen::def_enum: enum underlying type provided was not of type Typename: %s", type.debug_str() ); + return CodeInvalid; + } + + if ( attributes && attributes->Type != PlatformAttributes ) + { + log_failure( "gen::def_enum: attributes was not a 'PlatformAttributes' type: %s", attributes.debug_str() ); + return CodeInvalid; + } + + CodeEnum result = (CodeEnum)make_code(); + result->Name = get_cached_string( name ); + result->ModuleFlags = mflags; + + if ( body ) + { + switch ( body->Type ) + { + case Enum_Body : + case Untyped : + break; + + default : + log_failure( "gen::def_enum: body must be of Enum_Body or Untyped type %s", body.debug_str() ); + return CodeInvalid; + } + + result->Type = specifier == EnumClass ? Enum_Class : Enum; + + result->Body = body; + } + else + { + result->Type = specifier == EnumClass ? Enum_Class_Fwd : Enum_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( type ) + { + result->UnderlyingType = type; + } + else if ( result->Type != Enum_Class_Fwd && result->Type != Enum_Fwd ) + { + log_failure( "gen::def_enum: enum forward declaration must have an underlying type" ); + return CodeInvalid; + } + + return result; +} + +CodeExec def_execution( StrC content ) +{ + if ( content.Len <= 0 || content.Ptr == nullptr ) + { + log_failure( "gen::def_execution: Invalid execution provided" ); + return CodeInvalid; + } + + Code result = make_code(); + result->Type = ECode::Execution; + result->Name = get_cached_string( content ); + result->Content = result->Name; + + return (CodeExec)result; +} + +CodeExtern def_extern_link( StrC name, Code body ) +{ + using namespace ECode; + + name_check( def_extern_linkage, name ); + null_check( def_extern_linkage, body ); + + if ( body->Type != Extern_Linkage_Body && body->Type != Untyped ) + { + log_failure( "gen::def_extern_linkage: body is not of extern_linkage or untyped type %s", body->debug_str() ); + return CodeInvalid; + } + + CodeExtern result = (CodeExtern)make_code(); + result->Type = Extern_Linkage; + result->Name = get_cached_string( name ); + result->Body = body; + + return (CodeExtern)result; +} + +CodeFriend def_friend( Code declaration ) +{ + using namespace ECode; + + null_check( def_friend, declaration ); + + switch ( declaration->Type ) + { + case Class_Fwd : + case Function_Fwd : + case Operator_Fwd : + case Struct_Fwd : + case Class : + case Function : + case Operator : + case Struct : + break; + + default : + log_failure( "gen::def_friend: requires declartion to have class, function, operator, or struct - %s", declaration->debug_str() ); + return CodeInvalid; + } + + CodeFriend result = (CodeFriend)make_code(); + result->Type = Friend; + + result->Declaration = declaration; + + return result; +} + +CodeFn def_function( StrC name, CodeParam params, CodeType ret_type, Code body, CodeSpecifiers specifiers, CodeAttributes attributes, ModuleFlag mflags ) +{ + using namespace ECode; + + name_check( def_function, name ); + + if ( params && params->Type != Parameters ) + { + log_failure( "gen::def_function: params was not a `Parameters` type: %s", params.debug_str() ); + return CodeInvalid; + } + + if ( ret_type && ret_type->Type != Typename ) + { + log_failure( "gen::def_function: ret_type was not a Typename: %s", ret_type.debug_str() ); + return CodeInvalid; + } + + if ( specifiers && specifiers->Type != Specifiers ) + { + log_failure( "gen::def_function: specifiers was not a `Specifiers` type: %s", specifiers.debug_str() ); + return CodeInvalid; + } + + if ( attributes && attributes->Type != PlatformAttributes ) + { + log_failure( "gen::def_function: attributes was not a `PlatformAttributes` type: %s", attributes.debug_str() ); + return CodeInvalid; + } + + CodeFn result = (CodeFn)make_code(); + result->Name = get_cached_string( name ); + result->ModuleFlags = mflags; + + if ( body ) + { + switch ( body->Type ) + { + case Function_Body : + case Execution : + case Untyped : + break; + + default : + { + log_failure( "gen::def_function: body must be either of Function_Body, Execution, or Untyped type. %s", body->debug_str() ); + return CodeInvalid; + } + } + + result->Type = Function; + result->Body = body; + } + else + { + result->Type = Function_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( specifiers ) + result->Specs = specifiers; + + if ( ret_type ) + { + result->ReturnType = ret_type; + } + else + { + result->ReturnType = t_void; + } + + if ( params ) + result->Params = params; + + return result; +} + +CodeInclude def_include( StrC path, bool foreign ) +{ + if ( path.Len <= 0 || path.Ptr == nullptr ) + { + log_failure( "gen::def_include: Invalid path provided - %d" ); + return CodeInvalid; + } + + StrC content = foreign ? to_str( str_fmt_buf( "<%.*s>", path.Len, path.Ptr ) ) : to_str( str_fmt_buf( "\"%.*s\"", path.Len, path.Ptr ) ); + + Code result = make_code(); + result->Type = ECode::Preprocess_Include; + result->Name = get_cached_string( content ); + result->Content = result->Name; + + return (CodeInclude)result; +} + +CodeModule def_module( StrC name, ModuleFlag mflags ) +{ + name_check( def_module, name ); + + Code result = make_code(); + result->Type = ECode::Module; + result->Name = get_cached_string( name ); + result->Content = result->Name; + result->ModuleFlags = mflags; + + return (CodeModule)result; +} + +CodeNS def_namespace( StrC name, Code body, ModuleFlag mflags ) +{ + using namespace ECode; + + name_check( def_namespace, name ); + null_check( def_namespace, body ); + + if ( body->Type != Namespace_Body && body->Type != Untyped ) + { + log_failure( "gen::def_namespace: body is not of namespace or untyped type %s", body.debug_str() ); + return CodeInvalid; + } + + CodeNS result = (CodeNS)make_code(); + result->Type = Namespace; + result->Name = get_cached_string( name ); + result->ModuleFlags = mflags; + result->Body = body; + + return result; +} + +CodeOperator def_operator( + OperatorT op, + StrC nspace, + CodeParam params_code, + CodeType ret_type, + Code body, + CodeSpecifiers specifiers, + CodeAttributes attributes, + ModuleFlag mflags +) +{ + using namespace ECode; + + if ( attributes && attributes->Type != PlatformAttributes ) + { + log_failure( "gen::def_operator: PlatformAttributes was provided but its not of attributes type: %s", attributes.debug_str() ); + return CodeInvalid; + } + + if ( specifiers && specifiers->Type != Specifiers ) + { + log_failure( "gen::def_operator: Specifiers was provided but its not of specifiers type: %s", specifiers.debug_str() ); + return CodeInvalid; + } + + OpValidateResult check_result = operator__validate( op, params_code, ret_type, specifiers ); + + if ( check_result == OpValidateResult::Fail ) + { + return CodeInvalid; + } + + char const* name = nullptr; + + StrC op_str = to_str( op ); + if ( nspace.Len > 0 ) + name = str_fmt_buf( "%.*soperator %.*s", nspace.Len, nspace.Ptr, op_str.Len, op_str.Ptr ); + else + name = str_fmt_buf( "operator %.*s", op_str.Len, op_str.Ptr ); + CodeOperator result = (CodeOperator)make_code(); + result->Name = get_cached_string( { str_len( name ), name } ); + result->ModuleFlags = mflags; + result->Op = op; + + if ( body ) + { + switch ( body->Type ) + { + case Function_Body : + case Execution : + case Untyped : + break; + + default : + { + log_failure( "gen::def_operator: body must be either of Function_Body, Execution, or Untyped type. %s", body->debug_str() ); + return CodeInvalid; + } + } + + result->Type = check_result == OpValidateResult::Global ? Operator : Operator_Member; + + result->Body = body; + } + else + { + result->Type = check_result == OpValidateResult::Global ? Operator_Fwd : Operator_Member_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( specifiers ) + result->Specs = specifiers; + + result->ReturnType = ret_type; + + if ( params_code ) + result->Params = params_code; + + return result; +} + +CodeOpCast def_operator_cast( CodeType type, Code body, CodeSpecifiers const_spec ) +{ + using namespace ECode; + null_check( def_operator_cast, type ); + + if ( type->Type != Typename ) + { + log_failure( "gen::def_operator_cast: type is not a typename - %s", type.debug_str() ); + return CodeInvalid; + } + + CodeOpCast result = (CodeOpCast)make_code(); + + if ( body ) + { + result->Type = Operator_Cast; + + if ( body->Type != Function_Body && body->Type != Execution ) + { + log_failure( "gen::def_operator_cast: body is not of function body or execution type - %s", body.debug_str() ); + return CodeInvalid; + } + + result->Body = body; + } + else + { + result->Type = Operator_Cast_Fwd; + } + + if ( const_spec ) + { + result->Specs = const_spec; + } + + result->ValueType = type; + return result; +} + +CodeParam def_param( CodeType type, StrC name, Code value ) +{ + using namespace ECode; + + name_check( def_param, name ); + null_check( def_param, type ); + + if ( type->Type != Typename ) + { + log_failure( "gen::def_param: type is not a typename - %s", type.debug_str() ); + return CodeInvalid; + } + + if ( value && value->Type != Untyped ) + { + log_failure( "gen::def_param: value is not untyped - %s", value.debug_str() ); + return CodeInvalid; + } + + CodeParam result = (CodeParam)make_code(); + result->Type = Parameters; + result->Name = get_cached_string( name ); + + result->ValueType = type; + + if ( value ) + result->Value = value; + + result->NumEntries++; + + return result; +} + +CodePragma def_pragma( StrC directive ) +{ + using namespace ECode; + + if ( directive.Len <= 0 || directive.Ptr == nullptr ) + { + log_failure( "gen::def_comment: Invalid comment provided:" ); + return CodeInvalid; + } + + CodePragma result = (CodePragma)make_code(); + result->Type = Preprocess_Pragma; + result->Content = get_cached_string( directive ); + + return result; +} + +CodePreprocessCond def_preprocess_cond( EPreprocessCond type, StrC expr ) +{ + using namespace ECode; + + if ( expr.Len <= 0 || expr.Ptr == nullptr ) + { + log_failure( "gen::def_comment: Invalid comment provided:" ); + return CodeInvalid; + } + + CodePreprocessCond result = (CodePreprocessCond)make_code(); + result->Content = get_cached_string( expr ); + + switch ( type ) + { + case EPreprocessCond::If : + result->Type = Preprocess_If; + break; + case EPreprocessCond::IfDef : + result->Type = Preprocess_IfDef; + break; + case EPreprocessCond::IfNotDef : + result->Type = Preprocess_IfNotDef; + break; + case EPreprocessCond::ElIf : + result->Type = Preprocess_ElIf; + break; + } + + return result; +} + +CodeSpecifiers def_specifier( SpecifierT spec ) +{ + CodeSpecifiers result = (CodeSpecifiers)make_code(); + result->Type = ECode::Specifiers; + result.append( spec ); + + return result; +} + +CodeStruct def_struct( + StrC name, + Code body, + CodeType parent, + AccessSpec parent_access, + CodeAttributes attributes, + ModuleFlag mflags, + CodeType* interfaces, + s32 num_interfaces +) +{ + using namespace ECode; + + if ( attributes && attributes->Type != PlatformAttributes ) + { + log_failure( "gen::def_struct: attributes was not a `PlatformAttributes` type - %s", attributes.debug_str() ); + return CodeInvalid; + } + + if ( parent && parent->Type != Typename ) + { + log_failure( "gen::def_struct: parent was not a `Struct` type - %s", parent.debug_str() ); + return CodeInvalid; + } + + if ( body && body->Type != Struct_Body ) + { + log_failure( "gen::def_struct: body was not a Struct_Body type - %s", body.debug_str() ); + return CodeInvalid; + } + + CodeStruct result = (CodeStruct)make_code(); + result->ModuleFlags = mflags; + + if ( name ) + result->Name = get_cached_string( name ); + + if ( body ) + { + result->Type = Struct; + result->Body = body; + } + else + { + result->Type = Struct_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( parent ) + { + result->ParentAccess = parent_access; + result->ParentType = parent; + } + + if ( interfaces ) + { + for ( s32 idx = 0; idx < num_interfaces; idx++ ) + { + result.add_interface( interfaces[idx] ); + } + } + + return result; +} + +CodeTemplate def_template( CodeParam params, Code declaration, ModuleFlag mflags ) +{ + null_check( def_template, declaration ); + + if ( params && params->Type != ECode::Parameters ) + { + log_failure( "gen::def_template: params is not of parameters type - %s", params.debug_str() ); + return CodeInvalid; + } + + switch ( declaration->Type ) + { + case ECode::Class : + case ECode::Function : + case ECode::Struct : + case ECode::Variable : + case ECode::Using : + break; + + default : + log_failure( "gen::def_template: declaration is not of class, function, struct, variable, or using type - %s", declaration.debug_str() ); + } + + CodeTemplate result = (CodeTemplate)make_code(); + result->Type = ECode::Template; + result->ModuleFlags = mflags; + result->Params = params; + result->Declaration = declaration; + + return result; +} + +CodeType def_type( StrC name, Code arrayexpr, CodeSpecifiers specifiers, CodeAttributes attributes ) +{ + name_check( def_type, name ); + + if ( attributes && attributes->Type != ECode::PlatformAttributes ) + { + log_failure( "gen::def_type: attributes is not of attributes type - %s", attributes.debug_str() ); + return CodeInvalid; + } + + if ( specifiers && specifiers->Type != ECode::Specifiers ) + { + log_failure( "gen::def_type: specifiers is not of specifiers type - %s", specifiers.debug_str() ); + return CodeInvalid; + } + + if ( arrayexpr && arrayexpr->Type != ECode::Untyped ) + { + log_failure( "gen::def_type: arrayexpr is not of untyped type - %s", arrayexpr->debug_str() ); + return CodeInvalid; + } + + CodeType result = (CodeType)make_code(); + result->Name = get_cached_string( name ); + result->Type = ECode::Typename; + + if ( attributes ) + result->Attributes = attributes; + + if ( specifiers ) + result->Specs = specifiers; + + if ( arrayexpr ) + result->ArrExpr = arrayexpr; + + return result; +} + +CodeTypedef def_typedef( StrC name, Code type, CodeAttributes attributes, ModuleFlag mflags ) +{ + using namespace ECode; + + null_check( def_typedef, type ); + + switch ( type->Type ) + { + case Class : + case Class_Fwd : + case Enum : + case Enum_Fwd : + case Enum_Class : + case Enum_Class_Fwd : + case Function_Fwd : + case Struct : + case Struct_Fwd : + case Union : + case Typename : + break; + default : + log_failure( "gen::def_typedef: type was not a Class, Enum, Function Forward, Struct, Typename, or Union - %s", type.debug_str() ); + return CodeInvalid; + } + + if ( attributes && attributes->Type != ECode::PlatformAttributes ) + { + log_failure( "gen::def_typedef: attributes was not a PlatformAttributes - %s", attributes.debug_str() ); + return CodeInvalid; + } + + // Registering the type. + Code registered_type = def_type( name ); + + if ( ! registered_type ) + { + log_failure( "gen::def_typedef: failed to register type" ); + return CodeInvalid; + } + + CodeTypedef result = (CodeTypedef)make_code(); + result->Type = ECode::Typedef; + result->ModuleFlags = mflags; + + result->UnderlyingType = type; + + if ( name.Len <= 0 ) + { + if ( type->Type != Untyped ) + { + log_failure( "gen::def_typedef: name was empty and type was not untyped (indicating its a function typedef) - %s", type.debug_str() ); + return CodeInvalid; + } + + result->Name = get_cached_string( type->Name ); + result->IsFunction = true; + } + else + { + result->Name = get_cached_string( name ); + result->IsFunction = false; + } + + return result; +} + +CodeUnion def_union( StrC name, Code body, CodeAttributes attributes, ModuleFlag mflags ) +{ + null_check( def_union, body ); + + if ( body->Type != ECode::Union_Body ) + { + log_failure( "gen::def_union: body was not a Union_Body type - %s", body.debug_str() ); + return CodeInvalid; + } + + if ( attributes && attributes->Type != ECode::PlatformAttributes ) + { + log_failure( "gen::def_union: attributes was not a PlatformAttributes type - %s", attributes.debug_str() ); + return CodeInvalid; + } + + CodeUnion result = (CodeUnion)make_code(); + result->ModuleFlags = mflags; + result->Type = ECode::Union; + + if ( name.Ptr ) + result->Name = get_cached_string( name ); + + result->Body = body; + + if ( attributes ) + result->Attributes = attributes; + + return result; +} + +CodeUsing def_using( StrC name, CodeType type, CodeAttributes attributes, ModuleFlag mflags ) +{ + name_check( def_using, name ); + null_check( def_using, type ); + + Code register_type = def_type( name ); + + if ( ! register_type ) + { + log_failure( "gen::def_using: failed to register type" ); + return CodeInvalid; + } + + if ( attributes && attributes->Type != ECode::PlatformAttributes ) + { + log_failure( "gen::def_using: attributes was not a PlatformAttributes type - %s", attributes.debug_str() ); + return CodeInvalid; + } + + CodeUsing result = (CodeUsing)make_code(); + result->Name = get_cached_string( name ); + result->ModuleFlags = mflags; + result->Type = ECode::Using; + + result->UnderlyingType = type; + + if ( attributes ) + result->Attributes = attributes; + + return result; +} + +CodeUsing def_using_namespace( StrC name ) +{ + name_check( def_using_namespace, name ); + + Code result = make_code(); + result->Name = get_cached_string( name ); + result->Content = result->Name; + result->Type = ECode::Using_Namespace; + + return (CodeUsing)result; +} + +CodeVar def_variable( CodeType type, StrC name, Code value, CodeSpecifiers specifiers, CodeAttributes attributes, ModuleFlag mflags ) +{ + name_check( def_variable, name ); + null_check( def_variable, type ); + + if ( attributes && attributes->Type != ECode::PlatformAttributes ) + { + log_failure( "gen::def_variable: attributes was not a `PlatformAttributes` type - %s", attributes.debug_str() ); + return CodeInvalid; + } + + if ( specifiers && specifiers->Type != ECode::Specifiers ) + { + log_failure( "gen::def_variable: specifiers was not a `Specifiers` type - %s", specifiers.debug_str() ); + return CodeInvalid; + } + + if ( type->Type != ECode::Typename ) + { + log_failure( "gen::def_variable: type was not a Typename - %s", type.debug_str() ); + return CodeInvalid; + } + + if ( value && value->Type != ECode::Untyped ) + { + log_failure( "gen::def_variable: value was not a `Untyped` type - %s", value.debug_str() ); + return CodeInvalid; + } + + CodeVar result = (CodeVar)make_code(); + result->Name = get_cached_string( name ); + result->Type = ECode::Variable; + result->ModuleFlags = mflags; + + result->ValueType = type; + + if ( attributes ) + result->Attributes = attributes; + + if ( specifiers ) + result->Specs = specifiers; + + if ( value ) + result->Value = value; + + return result; +} + +#pragma region Helper Macros for def_**_body functions +#define def_body_start( Name_ ) \ + using namespace ECode; \ + \ + if ( num <= 0 ) \ + { \ + log_failure( "gen::" stringize( Name_ ) ": num cannot be zero or negative" ); \ + return CodeInvalid; \ + } + +#define def_body_code_array_start( Name_ ) \ + using namespace ECode; \ + \ + if ( num <= 0 ) \ + { \ + log_failure( "gen::" stringize( Name_ ) ": num cannot be zero or negative" ); \ + return CodeInvalid; \ + } \ + \ + if ( codes == nullptr ) \ + { \ + log_failure( "gen::" stringize( Name_ ) " : Provided a null array of codes" ); \ + return CodeInvalid; \ + } + +#pragma endregion Helper Macros for def_** _body functions + +CodeBody def_class_body( s32 num, ... ) +{ + def_body_start( def_class_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Class_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_class_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_CLASS_UNALLOWED_TYPES + log_failure( + "gen::" + "def_class_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_class_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_class_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Function_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_class_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_CLASS_UNALLOWED_TYPES + log_failure( + "gen::" + "def_class_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_enum_body( s32 num, ... ) +{ + def_body_start( def_enum_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Enum_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( "gen::def_enum_body: Provided a null entry" ); + return CodeInvalid; + } + + if ( entry->Type != Untyped && entry->Type != Comment ) + { + log_failure( "gen::def_enum_body: Entry type is not allowed - %s. Must be of untyped or comment type.", entry.debug_str() ); + return CodeInvalid; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return (CodeBody)result; +} + +CodeBody def_enum_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_enum_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Enum_Body; + + do + { + Code entry = *codes; + + if ( ! entry ) + { + log_failure( "gen::def_enum_body: Provided a null entry" ); + return CodeInvalid; + } + + if ( entry->Type != Untyped && entry->Type != Comment ) + { + log_failure( "gen::def_enum_body: Entry type is not allowed: %s", entry.debug_str() ); + return CodeInvalid; + } + + result.append( entry ); + } while ( codes++, num--, num > 0 ); + + return result; +} + +CodeBody def_export_body( s32 num, ... ) +{ + def_body_start( def_export_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Export_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_export_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_EXPORT_UNALLOWED_TYPES + log_failure( + "gen::" + "def_export_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_export_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_export_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Export_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_export_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_EXPORT_UNALLOWED_TYPES + log_failure( + "gen::" + "def_export_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_extern_link_body( s32 num, ... ) +{ + def_body_start( def_extern_linkage_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Extern_Linkage_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_extern_linkage_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_EXTERN_LINKAGE_UNALLOWED_TYPES + log_failure( + "gen::" + "def_extern_linkage_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_extern_link_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_extern_linkage_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Extern_Linkage_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_extern_linkage_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_EXTERN_LINKAGE_UNALLOWED_TYPES + log_failure( + "gen::" + "def_extern_linkage_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_function_body( s32 num, ... ) +{ + def_body_start( def_function_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Function_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( "gen::" stringize( def_function_body ) ": Provided an null entry" ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + + GEN_AST_BODY_FUNCTION_UNALLOWED_TYPES + log_failure( "gen::" stringize( def_function_body ) ": Entry type is not allowed: %s", entry.debug_str() ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_function_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_function_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Function_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_function_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_FUNCTION_UNALLOWED_TYPES + log_failure( + "gen::" + "def_function_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_global_body( s32 num, ... ) +{ + def_body_start( def_global_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Global_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_global_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + case Global_Body : + result.append( entry.cast() ); + continue; + + GEN_AST_BODY_GLOBAL_UNALLOWED_TYPES + log_failure( + "gen::" + "def_global_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return ( *Code::Invalid.ast ); + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_global_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_global_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Global_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_global_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + case Global_Body : + result.append( entry.cast() ); + continue; + + GEN_AST_BODY_GLOBAL_UNALLOWED_TYPES + log_failure( + "gen::" + "def_global_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_namespace_body( s32 num, ... ) +{ + def_body_start( def_namespace_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Namespace_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_namespace_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_NAMESPACE_UNALLOWED_TYPES + log_failure( + "gen::" + "def_namespace_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_namespace_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_namespace_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Global_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_namespace_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_NAMESPACE_UNALLOWED_TYPES + log_failure( + "gen::" + "def_namespace_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeParam def_params( s32 num, ... ) +{ + def_body_start( def_params ); + + va_list va; + va_start( va, num ); + + Code_POD pod = va_arg( va, Code_POD ); + CodeParam param = pcast( CodeParam, pod ); + + null_check( def_params, param ); + + if ( param->Type != Parameters ) + { + log_failure( "gen::def_params: param %d is not a Parameters", num - num + 1 ); + return CodeInvalid; + } + + CodeParam result = (CodeParam)param.duplicate(); + + while ( --num ) + { + pod = va_arg( va, Code_POD ); + param = pcast( CodeParam, pod ); + + if ( param->Type != Parameters ) + { + log_failure( "gen::def_params: param %d is not a Parameters", num - num + 1 ); + return CodeInvalid; + } + + result.append( param ); + } + va_end( va ); + + return result; +} + +CodeParam def_params( s32 num, CodeParam* codes ) +{ + def_body_code_array_start( def_params ); + +#define check_current() \ + if ( current.ast == nullptr ) \ + { \ + log_failure( "gen::def_params: Provide a null code in codes array" ); \ + return CodeInvalid; \ + } \ + \ + if ( current->Type != Parameters ) \ + { \ + log_failure( "gen::def_params: Code in coes array is not of paramter type - %s", current.debug_str() ); \ + return CodeInvalid; \ + } + + CodeParam current = (CodeParam)codes->duplicate(); + check_current(); + + CodeParam result = (CodeParam)make_code(); + result->Name = current->Name; + result->Type = current->Type; + result->ValueType = current->ValueType; + + while ( codes++, current = *codes, num--, num > 0 ) + { + check_current(); + result.append( current ); + } +#undef check_current + + return result; +} + +CodeSpecifiers def_specifiers( s32 num, ... ) +{ + if ( num <= 0 ) + { + log_failure( "gen::def_specifiers: num cannot be zero or less" ); + return CodeInvalid; + } + + if ( num > AST::ArrSpecs_Cap ) + { + log_failure( "gen::def_specifiers: num of speciifers to define AST larger than AST specicifier capacity - %d", num ); + return CodeInvalid; + } + + CodeSpecifiers result = (CodeSpecifiers)make_code(); + result->Type = ECode::Specifiers; + + va_list va; + va_start( va, num ); + do + { + SpecifierT type = (SpecifierT)va_arg( va, int ); + + result.append( type ); + } while ( --num, num ); + va_end( va ); + + return result; +} + +CodeSpecifiers def_specifiers( s32 num, SpecifierT* specs ) +{ + if ( num <= 0 ) + { + log_failure( "gen::def_specifiers: num cannot be zero or less" ); + return CodeInvalid; + } + + if ( num > AST::ArrSpecs_Cap ) + { + log_failure( "gen::def_specifiers: num of speciifers to define AST larger than AST specicifier capacity - %d", num ); + return CodeInvalid; + } + + CodeSpecifiers result = (CodeSpecifiers)make_code(); + result->Type = ECode::Specifiers; + + s32 idx = 0; + do + { + result.append( specs[idx] ); + idx++; + } while ( --num, num ); + + return result; +} + +CodeBody def_struct_body( s32 num, ... ) +{ + def_body_start( def_struct_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Struct_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( + "gen::" + "def_struct_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_STRUCT_UNALLOWED_TYPES + log_failure( + "gen::" + "def_struct_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_struct_body( s32 num, Code* codes ) +{ + def_body_code_array_start( def_struct_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Struct_Body; + + do + { + Code entry = *codes; + codes++; + + if ( ! entry ) + { + log_failure( + "gen::" + "def_struct_body" + ": Provided an null entry" + ); + return CodeInvalid; + } + + switch ( entry->Type ) + { + GEN_AST_BODY_STRUCT_UNALLOWED_TYPES + log_failure( + "gen::" + "def_struct_body" + ": Entry type is not allowed: %s", + entry.debug_str() + ); + return CodeInvalid; + + default : + break; + } + + result.append( entry ); + } while ( num--, num > 0 ); + + return result; +} + +CodeBody def_union_body( s32 num, ... ) +{ + def_body_start( def_union_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Union_Body; + + va_list va; + va_start( va, num ); + do + { + Code_POD pod = va_arg( va, Code_POD ); + Code entry = pcast( Code, pod ); + + if ( ! entry ) + { + log_failure( "gen::def_union_body: Provided a null entry" ); + return CodeInvalid; + } + + if ( entry->Type != Untyped && entry->Type != Comment ) + { + log_failure( "gen::def_union_body: Entry type is not allowed - %s. Must be of untyped or comment type.", entry.debug_str() ); + return CodeInvalid; + } + + result.append( entry ); + } while ( num--, num > 0 ); + va_end( va ); + + return result; +} + +CodeBody def_union_body( s32 num, CodeUnion* codes ) +{ + def_body_code_array_start( def_union_body ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Union_Body; + + do + { + Code entry = *codes; + + if ( ! entry ) + { + log_failure( "gen::def_union_body: Provided a null entry" ); + return CodeInvalid; + } + + if ( entry->Type != Untyped && entry->Type != Comment ) + { + log_failure( "gen::def_union_body: Entry type is not allowed: %s", entry.debug_str() ); + return CodeInvalid; + } + + result.append( entry ); + } while ( codes++, num--, num > 0 ); + + return (CodeBody)result; +} + +#undef name_check +#undef null_check +#undef null_or_invalid_check +#undef def_body_start +#undef def_body_code_array_start + +#pragma endregion Upfront + +#pragma region Parsing + +namespace parser +{ + namespace ETokType + { +#define GEN_DEFINE_ATTRIBUTE_TOKENS \ + Entry( Attribute_API_Export, "GEN_API_Export_Code" ) Entry( Attribute_API_Import, "GEN_API_Import_Code" ) \ + Entry( Attribute_UE_DEPRECATED, "UE_DEPRECATED" ) Entry( Attribute_UMG_API, "UMG_API" ) Entry( Attribute_COREUOBJECT_API, "COREUOBJECT_API" ) \ + Entry( Attribute_ENGINE_API, "ENGINE_API" ) Entry( Attribute_GASA_API, "GASA_API" ) \ + Entry( Attribute_GAMEPLAYABILITIES_API, "GAMEPLAYABILITIES_API" ) + + enum Type : u32 + { + Invalid, + Access_Private, + Access_Protected, + Access_Public, + Access_MemberSymbol, + Access_StaticSymbol, + Ampersand, + Ampersand_DBL, + Assign_Classifer, + Attribute_Open, + Attribute_Close, + BraceCurly_Open, + BraceCurly_Close, + BraceSquare_Open, + BraceSquare_Close, + Capture_Start, + Capture_End, + Comment, + Comment_End, + Comment_Start, + Char, + Comma, + Decl_Class, + Decl_GNU_Attribute, + Decl_MSVC_Attribute, + Decl_Enum, + Decl_Extern_Linkage, + Decl_Friend, + Decl_Module, + Decl_Namespace, + Decl_Operator, + Decl_Struct, + Decl_Template, + Decl_Typedef, + Decl_Using, + Decl_Union, + Identifier, + Module_Import, + Module_Export, + NewLine, + Number, + Operator, + Preprocess_Hash, + Preprocess_Define, + Preprocess_If, + Preprocess_IfDef, + Preprocess_IfNotDef, + Preprocess_ElIf, + Preprocess_Else, + Preprocess_EndIf, + Preprocess_Include, + Preprocess_Pragma, + Preprocess_Content, + Preprocess_Macro, + Preprocess_Unsupported, + Spec_Alignas, + Spec_Const, + Spec_Consteval, + Spec_Constexpr, + Spec_Constinit, + Spec_Explicit, + Spec_Extern, + Spec_Final, + Spec_ForceInline, + Spec_Global, + Spec_Inline, + Spec_Internal_Linkage, + Spec_LocalPersist, + Spec_Mutable, + Spec_NeverInline, + Spec_Override, + Spec_Static, + Spec_ThreadLocal, + Spec_Volatile, + Spec_Virtual, + Star, + Statement_End, + StaticAssert, + String, + Type_Typename, + Type_Unsigned, + Type_Signed, + Type_Short, + Type_Long, + Type_bool, + Type_char, + Type_int, + Type_double, + Type_MS_int8, + Type_MS_int16, + Type_MS_int32, + Type_MS_int64, + Type_MS_W64, + Varadic_Argument, + __Attributes_Start, + Attribute_API_Export, + Attribute_API_Import, + Attribute_UE_DEPRECATED, + Attribute_UMG_API, + Attribute_COREUOBJECT_API, + Attribute_ENGINE_API, + Attribute_GASA_API, + Attribute_GAMEPLAYABILITIES_API, + NumTokens + }; + + StrC to_str( Type type ) + { + local_persist StrC lookup[] { + { sizeof( "__invalid__" ), "__invalid__" }, + { sizeof( "private" ), "private" }, + { sizeof( "protected" ), "protected" }, + { sizeof( "public" ), "public" }, + { sizeof( "." ), "." }, + { sizeof( "::" ), "::" }, + { sizeof( "&" ), "&" }, + { sizeof( "&&" ), "&&" }, + { sizeof( ":" ), ":" }, + { sizeof( "[[" ), "[[" }, + { sizeof( "]]" ), "]]" }, + { sizeof( "{" ), "{" }, + { sizeof( "}" ), "}" }, + { sizeof( "[" ), "[" }, + { sizeof( "]" ), "]" }, + { sizeof( "(" ), "(" }, + { sizeof( ")" ), ")" }, + { sizeof( "__comment__" ), "__comment__" }, + { sizeof( "__comment_end__" ), "__comment_end__" }, + { sizeof( "__comment_start__" ), "__comment_start__" }, + { sizeof( "__character__" ), "__character__" }, + { sizeof( "," ), "," }, + { sizeof( "class" ), "class" }, + { sizeof( "__attribute__" ), "__attribute__" }, + { sizeof( "__declspec" ), "__declspec" }, + { sizeof( "enum" ), "enum" }, + { sizeof( "extern" ), "extern" }, + { sizeof( "friend" ), "friend" }, + { sizeof( "module" ), "module" }, + { sizeof( "namespace" ), "namespace" }, + { sizeof( "operator" ), "operator" }, + { sizeof( "struct" ), "struct" }, + { sizeof( "template" ), "template" }, + { sizeof( "typedef" ), "typedef" }, + { sizeof( "using" ), "using" }, + { sizeof( "union" ), "union" }, + { sizeof( "__identifier__" ), "__identifier__" }, + { sizeof( "import" ), "import" }, + { sizeof( "export" ), "export" }, + { sizeof( "__new_line__" ), "__new_line__" }, + { sizeof( "__number__" ), "__number__" }, + { sizeof( "__operator__" ), "__operator__" }, + { sizeof( "#" ), "#" }, + { sizeof( "define" ), "define" }, + { sizeof( "if" ), "if" }, + { sizeof( "ifdef" ), "ifdef" }, + { sizeof( "ifndef" ), "ifndef" }, + { sizeof( "elif" ), "elif" }, + { sizeof( "else" ), "else" }, + { sizeof( "endif" ), "endif" }, + { sizeof( "include" ), "include" }, + { sizeof( "pragma" ), "pragma" }, + { sizeof( "__macro_content__" ), "__macro_content__" }, + { sizeof( "__macro__" ), "__macro__" }, + { sizeof( "__unsupported__" ), "__unsupported__" }, + { sizeof( "alignas" ), "alignas" }, + { sizeof( "const" ), "const" }, + { sizeof( "consteval" ), "consteval" }, + { sizeof( "constexpr" ), "constexpr" }, + { sizeof( "constinit" ), "constinit" }, + { sizeof( "explicit" ), "explicit" }, + { sizeof( "extern" ), "extern" }, + { sizeof( "final" ), "final" }, + { sizeof( "FORCEINLINE" ), "FORCEINLINE" }, + { sizeof( "global" ), "global" }, + { sizeof( "inline" ), "inline" }, + { sizeof( "internal" ), "internal" }, + { sizeof( "local_persist" ), "local_persist" }, + { sizeof( "mutable" ), "mutable" }, + { sizeof( "neverinline" ), "neverinline" }, + { sizeof( "override" ), "override" }, + { sizeof( "static" ), "static" }, + { sizeof( "thread_local" ), "thread_local" }, + { sizeof( "volatile" ), "volatile" }, + { sizeof( "virtual" ), "virtual" }, + { sizeof( "*" ), "*" }, + { sizeof( ";" ), ";" }, + { sizeof( "static_assert" ), "static_assert" }, + { sizeof( "__string__" ), "__string__" }, + { sizeof( "typename" ), "typename" }, + { sizeof( "unsigned" ), "unsigned" }, + { sizeof( "signed" ), "signed" }, + { sizeof( "short" ), "short" }, + { sizeof( "long" ), "long" }, + { sizeof( "bool" ), "bool" }, + { sizeof( "char" ), "char" }, + { sizeof( "int" ), "int" }, + { sizeof( "double" ), "double" }, + { sizeof( "__int8" ), "__int8" }, + { sizeof( "__int16" ), "__int16" }, + { sizeof( "__int32" ), "__int32" }, + { sizeof( "__int64" ), "__int64" }, + { sizeof( "_W64" ), "_W64" }, + { sizeof( "..." ), "..." }, + { sizeof( "__attrib_start__" ), "__attrib_start__" }, + { sizeof( "GEN_API_Export_Code" ), "GEN_API_Export_Code" }, + { sizeof( "GEN_API_Import_Code" ), "GEN_API_Import_Code" }, + { sizeof( "UE_DEPRECATED" ), "UE_DEPRECATED" }, + { sizeof( "UMG_API" ), "UMG_API" }, + { sizeof( "COREUOBJECT_API" ), "COREUOBJECT_API" }, + { sizeof( "ENGINE_API" ), "ENGINE_API" }, + { sizeof( "GASA_API" ), "GASA_API" }, + { sizeof( "GAMEPLAYABILITIES_API" ), "GAMEPLAYABILITIES_API" }, + }; + return lookup[type]; + } + + Type to_type( StrC str ) + { + local_persist u32 keymap[NumTokens]; + do_once_start for ( u32 index = 0; index < NumTokens; index++ ) + { + StrC enum_str = to_str( (Type)index ); + keymap[index] = crc32( enum_str.Ptr, enum_str.Len - 1 ); + } + do_once_end u32 hash = crc32( str.Ptr, str.Len ); + for ( u32 index = 0; index < NumTokens; index++ ) + { + if ( keymap[index] == hash ) + return (Type)index; + } + return Invalid; + } + + } // namespace ETokType + + using TokType = ETokType::Type; + +} // namespace parser + +namespace parser +{ + + enum TokFlags : u32 + { + TF_Operator = bit( 0 ), + TF_Assign = bit( 1 ), + TF_Preprocess = bit( 2 ), + TF_Preprocess_Cond = bit( 3 ), + TF_Attribute = bit( 6 ), + TF_AccessOperator = bit( 7 ), + TF_AccessSpecifier = bit( 8 ), + TF_Specifier = bit( 9 ), + TF_EndDefinition = bit( 10 ), // Either ; or } + TF_Formatting = bit( 11 ), + TF_Literal = bit( 12 ), + + TF_Null = 0, + }; + + struct Token + { + char const* Text; + sptr Length; + TokType Type; + s32 Line; + s32 Column; + u32 Flags; + + operator bool() + { + return Text && Length && Type != TokType::Invalid; + } + + operator StrC() + { + return { Length, Text }; + } + + bool is_access_operator() + { + return bitfield_is_equal( u32, Flags, TF_AccessOperator ); + } + + bool is_access_specifier() + { + return bitfield_is_equal( u32, Flags, TF_AccessSpecifier ); + } + + bool is_attribute() + { + return bitfield_is_equal( u32, Flags, TF_Attribute ); + } + + bool is_operator() + { + return bitfield_is_equal( u32, Flags, TF_Operator ); + } + + bool is_preprocessor() + { + return bitfield_is_equal( u32, Flags, TF_Preprocess ); + } + + bool is_preprocess_cond() + { + return bitfield_is_equal( u32, Flags, TF_Preprocess_Cond ); + } + + bool is_specifier() + { + return bitfield_is_equal( u32, Flags, TF_Specifier ); + } + + bool is_end_definition() + { + return bitfield_is_equal( u32, Flags, TF_EndDefinition ); + } + + AccessSpec to_access_specifier() + { + return scast( AccessSpec, Type ); + } + + String to_string() + { + String result = String::make_reserve( GlobalAllocator, kilobytes( 4 ) ); + + StrC type_str = ETokType::to_str( Type ); + + result.append_fmt( "Line: %d Column: %d, Type: %.*s Content: %.*s", Line, Column, type_str.Len, type_str.Ptr, Length, Text ); + + return result; + } + }; + + constexpr Token NullToken { nullptr, 0, TokType::Invalid, false, 0, TF_Null }; + + struct TokArray + { + Array Arr; + s32 Idx; + + bool __eat( TokType type ); + + Token& current( bool skip_formatting = true ) + { + if ( skip_formatting ) + { + while ( Arr[Idx].Type == TokType::NewLine || Arr[Idx].Type == TokType::Comment ) + Idx++; + } + + return Arr[Idx]; + } + + Token& previous( bool skip_formatting = false ) + { + s32 idx = this->Idx; + + if ( skip_formatting ) + { + while ( Arr[idx].Type == TokType::NewLine ) + idx--; + + return Arr[idx]; + } + + return Arr[idx - 1]; + } + + Token& next( bool skip_formatting = false ) + { + s32 idx = this->Idx; + + if ( skip_formatting ) + { + while ( Arr[idx].Type == TokType::NewLine ) + idx++; + + return Arr[idx + 1]; + } + + return Arr[idx + 1]; + } + + Token& operator[]( s32 idx ) + { + return Arr[idx]; + } + }; + + global Arena_256KB defines_map_arena; + global HashTable defines; + global Array Tokens; + +#define current ( *scanner ) + +#define move_forward() \ + { \ + if ( current == '\n' ) \ + { \ + line++; \ + column = 1; \ + } \ + else \ + { \ + column++; \ + } \ + left--; \ + scanner++; \ + } + +#define SkipWhitespace() \ + while ( left && char_is_space( current ) ) \ + { \ + move_forward(); \ + } + +#define end_line() \ + do \ + { \ + while ( left && current == ' ' ) \ + { \ + move_forward(); \ + } \ + if ( left && current == '\r' ) \ + { \ + move_forward(); \ + move_forward(); \ + } \ + else if ( left && current == '\n' ) \ + { \ + move_forward(); \ + } \ + } while ( 0 ) + + enum + { + Lex_Continue, + Lex_ReturnNull, + }; + + FORCEINLINE s32 lex_preprocessor_directive( StrC& content, s32& left, char const*& scanner, s32& line, s32& column, HashTable& preprocessor_defines, Token& token ) + { + char const* hash = scanner; + Tokens.append( { hash, 1, TokType::Preprocess_Hash, line, column, TF_Preprocess } ); + + move_forward(); + SkipWhitespace(); + + token.Text = scanner; + while ( left && ! char_is_space( current ) ) + { + move_forward(); + token.Length++; + } + + token.Type = ETokType::to_type( token ); + + bool is_preprocessor = token.Type >= TokType::Preprocess_Define && token.Type <= TokType::Preprocess_Pragma; + if ( ! is_preprocessor ) + { + token.Type = TokType::Preprocess_Unsupported; + + // Its an unsupported directive, skip it + s32 within_string = false; + s32 within_char = false; + while ( left ) + { + if ( current == '"' && ! within_char ) + within_string ^= true; + + if ( current == '\'' && ! within_string ) + within_char ^= true; + + if ( current == '\\' && ! within_string && ! within_char ) + { + move_forward(); + token.Length++; + + if ( current == '\r' ) + { + move_forward(); + token.Length++; + } + + if ( current == '\n' ) + { + move_forward(); + token.Length++; + continue; + } + else + { + log_failure( + "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" + " in preprocessor directive (%d, %d)\n%.100s", + current, + line, + column, + token.Line, + token.Column, + token.Text + ); + break; + } + } + + if ( current == '\r' ) + { + move_forward(); + token.Length++; + } + + if ( current == '\n' ) + { + move_forward(); + token.Length++; + break; + } + + move_forward(); + token.Length++; + } + + token.Length = token.Length + token.Text - hash; + token.Text = hash; + Tokens.append( token ); + return Lex_Continue; // Skip found token, its all handled here. + } + + if ( token.Type == TokType::Preprocess_Else || token.Type == TokType::Preprocess_EndIf ) + { + token.Flags |= TF_Preprocess_Cond; + Tokens.append( token ); + end_line(); + return Lex_Continue; + } + else if ( token.Type >= TokType::Preprocess_If && token.Type <= TokType::Preprocess_ElIf ) + { + token.Flags |= TF_Preprocess_Cond; + } + + Tokens.append( token ); + + SkipWhitespace(); + + if ( token.Type == TokType::Preprocess_Define ) + { + Token name = { scanner, 0, TokType::Identifier, line, column, TF_Preprocess }; + + name.Text = scanner; + name.Length = 1; + move_forward(); + + while ( left && ( char_is_alphanumeric( current ) || current == '_' ) ) + { + move_forward(); + name.Length++; + } + + if ( left && current == '(' ) + { + move_forward(); + name.Length++; + } + + Tokens.append( name ); + + u64 key = crc32( name.Text, name.Length ); + preprocessor_defines.set( key, name ); + } + + Token preprocess_content = { scanner, 0, TokType::Preprocess_Content, line, column, TF_Preprocess }; + + if ( token.Type == TokType::Preprocess_Include ) + { + preprocess_content.Type = TokType::String; + + if ( current != '"' && current != '<' ) + { + String directive_str = String::fmt_buf( GlobalAllocator, "%.*s", min( 80, left + preprocess_content.Length ), token.Text ); + + log_failure( + "gen::Parser::lex: Expected '\"' or '<' after #include, not '%c' (%d, %d)\n%s", + current, + preprocess_content.Line, + preprocess_content.Column, + directive_str.Data + ); + return Lex_ReturnNull; + } + move_forward(); + preprocess_content.Length++; + + while ( left && current != '"' && current != '>' ) + { + move_forward(); + preprocess_content.Length++; + } + + move_forward(); + preprocess_content.Length++; + + if ( current == '\r' && scanner[1] == '\n' ) + { + move_forward(); + move_forward(); + } + else if ( current == '\n' ) + { + move_forward(); + } + + Tokens.append( preprocess_content ); + return Lex_Continue; // Skip found token, its all handled here. + } + + s32 within_string = false; + s32 within_char = false; + + // SkipWhitespace(); + while ( left ) + { + if ( current == '"' && ! within_char ) + within_string ^= true; + + if ( current == '\'' && ! within_string ) + within_char ^= true; + + if ( current == '\\' && ! within_string && ! within_char ) + { + move_forward(); + preprocess_content.Length++; + + if ( current == '\r' ) + { + move_forward(); + preprocess_content.Length++; + } + + if ( current == '\n' ) + { + move_forward(); + preprocess_content.Length++; + continue; + } + else + { + String directive_str = String::make_length( GlobalAllocator, token.Text, token.Length ); + String content_str = String::fmt_buf( GlobalAllocator, "%.*s", min( 400, left + preprocess_content.Length ), preprocess_content.Text ); + + log_failure( + "gen::Parser::lex: Invalid escape sequence '\\%c' (%d, %d)" + " in preprocessor directive '%s' (%d, %d)\n%s", + current, + line, + column, + directive_str, + preprocess_content.Line, + preprocess_content.Column, + content_str + ); + break; + } + } + + if ( current == '\r' ) + { + move_forward(); + } + + if ( current == '\n' ) + { + move_forward(); + break; + } + + move_forward(); + preprocess_content.Length++; + } + + Tokens.append( preprocess_content ); + return Lex_Continue; // Skip found token, its all handled here. + } + + FORCEINLINE void lex_found_token( StrC& content, s32& left, char const*& scanner, s32& line, s32& column, HashTable& preprocessor_defines, Token& token ) + { + if ( token.Type != TokType::Invalid ) + { + Tokens.append( token ); + return; + } + + TokType type = ETokType::to_type( token ); + + if ( type <= TokType::Access_Public && type >= TokType::Access_Private ) + { + token.Flags |= TF_AccessSpecifier; + } + + if ( type > TokType::__Attributes_Start ) + { + token.Flags |= TF_Attribute; + } + + if ( type == ETokType::Decl_Extern_Linkage ) + { + SkipWhitespace(); + + if ( current != '"' ) + { + type = ETokType::Spec_Extern; + token.Flags |= TF_Specifier; + } + + token.Type = type; + Tokens.append( token ); + return; + } + + if ( ( type <= TokType::Star && type >= TokType::Spec_Alignas ) || type == TokType::Ampersand || type == TokType::Ampersand_DBL ) + { + token.Type = type; + token.Flags |= TF_Specifier; + Tokens.append( token ); + return; + } + + + if ( type != TokType::Invalid ) + { + token.Type = type; + Tokens.append( token ); + return; + } + + u64 key = 0; + if ( current == '(' ) + key = crc32( token.Text, token.Length + 1 ); + else + key = crc32( token.Text, token.Length ); + + StrC* define = preprocessor_defines.get( key ); + if ( define ) + { + token.Type = TokType::Preprocess_Macro; + + // Want to ignore any arguments the define may have as they can be execution expressions. + if ( left && current == '(' ) + { + move_forward(); + token.Length++; + + s32 level = 0; + while ( left && ( current != ')' || level > 0 ) ) + { + if ( current == '(' ) + level++; + + else if ( current == ')' && level > 0 ) + level--; + + move_forward(); + token.Length++; + } + + move_forward(); + token.Length++; + } + + if ( current == '\r' && scanner[1] == '\n' ) + { + move_forward(); + } + else if ( current == '\n' ) + { + move_forward(); + } + } + else + { + token.Type = TokType::Identifier; + } + + Tokens.append( token ); + } + + neverinline + // TokArray lex( Array tokens, StrC content ) + TokArray + lex( StrC content ) + { + s32 left = content.Len; + char const* scanner = content.Ptr; + + char const* word = scanner; + s32 word_length = 0; + + s32 line = 1; + s32 column = 1; + + SkipWhitespace(); + if ( left <= 0 ) + { + log_failure( "gen::lex: no tokens found (only whitespace provided)" ); + return { { nullptr }, 0 }; + } + + for ( StringCached entry : PreprocessorDefines ) + { + s32 length = 0; + char const* scanner = entry.Data; + while ( entry.length() > length && char_is_alphanumeric( *scanner ) || *scanner == '_' ) + { + scanner++; + length++; + } + if ( scanner[0] == '(' ) + { + length++; + } + + u64 key = crc32( entry.Data, length ); + defines.set( key, entry ); + } + + Tokens.clear(); + + while ( left ) + { +#if 0 + if (Tokens.num()) + { + log_fmt("\nLastTok: %S", Tokens.back().to_string()); + } +#endif + + Token token = { scanner, 0, TokType::Invalid, line, column, TF_Null }; + + bool is_define = false; + + if ( column == 1 ) + { + if ( current == '\r' ) + { + move_forward(); + token.Length = 1; + } + + if ( current == '\n' ) + { + move_forward(); + + token.Type = TokType::NewLine; + token.Length++; + + Tokens.append( token ); + continue; + } + } + + token.Length = 0; + + SkipWhitespace(); + if ( left <= 0 ) + break; + + switch ( current ) + { + case '#' : + { + s32 result = lex_preprocessor_directive( content, left, scanner, line, column, defines, token ); + switch ( result ) + { + case Lex_Continue : + continue; + + case Lex_ReturnNull : + return { { nullptr }, 0 }; + } + } + case '.' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Access_MemberSymbol; + token.Flags = TF_AccessOperator; + + if ( left ) + { + move_forward(); + } + + if ( current == '.' ) + { + move_forward(); + if ( current == '.' ) + { + token.Length = 3; + token.Type = TokType::Varadic_Argument; + token.Flags = TF_Null; + move_forward(); + } + else + { + String context_str = String::fmt_buf( GlobalAllocator, "%s", scanner, min( 100, left ) ); + + log_failure( "gen::lex: invalid varadic argument, expected '...' got '..%c' (%d, %d)\n%s", current, line, column, context_str ); + } + } + + goto FoundToken; + } + case '&' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Ampersand; + token.Flags |= TF_Operator; + token.Flags |= TF_Specifier; + + if ( left ) + move_forward(); + + if ( current == '&' ) // && + { + token.Length = 2; + token.Type = TokType::Ampersand_DBL; + + if ( left ) + move_forward(); + } + + goto FoundToken; + } + case ':' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Assign_Classifer; + // Can be either a classifier (ParentType, Bitfield width), or ternary else + // token.Type = TokType::Colon; + + if ( left ) + move_forward(); + + if ( current == ':' ) + { + move_forward(); + token.Type = TokType::Access_StaticSymbol; + token.Length++; + } + goto FoundToken; + } + case '{' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::BraceCurly_Open; + + if ( left ) + move_forward(); + goto FoundToken; + } + case '}' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::BraceCurly_Close; + token.Flags = TF_EndDefinition; + + if ( left ) + move_forward(); + + end_line(); + goto FoundToken; + } + case '[' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::BraceSquare_Open; + if ( left ) + { + move_forward(); + + if ( current == ']' ) + { + token.Length = 2; + token.Type = TokType::Operator; + move_forward(); + } + } + goto FoundToken; + } + case ']' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::BraceSquare_Close; + + if ( left ) + move_forward(); + goto FoundToken; + } + case '(' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Capture_Start; + + if ( left ) + move_forward(); + goto FoundToken; + } + case ')' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Capture_End; + + if ( left ) + move_forward(); + goto FoundToken; + } + case '\'' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Char; + token.Flags = TF_Literal; + + move_forward(); + + if ( left && current == '\\' ) + { + move_forward(); + token.Length++; + + if ( current == '\'' ) + { + move_forward(); + token.Length++; + } + } + + while ( left && current != '\'' ) + { + move_forward(); + token.Length++; + } + + if ( left ) + { + move_forward(); + token.Length++; + } + goto FoundToken; + } + case ',' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Comma; + token.Flags = TF_Operator; + + if ( left ) + move_forward(); + goto FoundToken; + } + case '*' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Star; + token.Flags |= TF_Specifier; + token.Flags |= TF_Operator; + + if ( left ) + move_forward(); + + if ( current == '=' ) + { + token.Length++; + token.Flags |= TF_Assign; + // token.Type = TokType::Assign_Multiply; + + if ( left ) + move_forward(); + } + + goto FoundToken; + } + case ';' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Statement_End; + token.Flags = TF_EndDefinition; + + if ( left ) + move_forward(); + + end_line(); + goto FoundToken; + } + case '"' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::String; + token.Flags |= TF_Literal; + + move_forward(); + while ( left ) + { + if ( current == '"' ) + { + move_forward(); + break; + } + + if ( current == '\\' ) + { + move_forward(); + token.Length++; + + if ( left ) + { + move_forward(); + token.Length++; + } + continue; + } + + move_forward(); + token.Length++; + } + goto FoundToken; + } + case '?' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Operator; + // token.Type = TokType::Ternary; + token.Flags = TF_Operator; + + if ( left ) + move_forward(); + + goto FoundToken; + } + case '=' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Operator; + // token.Type = TokType::Assign; + token.Flags = TF_Operator; + token.Flags |= TF_Assign; + + if ( left ) + move_forward(); + + if ( current == '=' ) + { + token.Length++; + token.Flags = TF_Operator; + + if ( left ) + move_forward(); + } + + goto FoundToken; + } + case '+' : + { + // token.Type = TokType::Add + } + case '%' : + { + // token.Type = TokType::Modulo; + } + case '^' : + { + // token.Type = TokType::B_XOr; + } + case '~' : + { + // token.Type = TokType::Unary_Not; + } + case '!' : + { + // token.Type = TokType::L_Not; + } + case '<' : + { + // token.Type = TokType::Lesser; + } + case '>' : + { + // token.Type = TokType::Greater; + } + case '|' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Operator; + token.Flags = TF_Operator; + // token.Type = TokType::L_Or; + + if ( left ) + move_forward(); + + if ( current == '=' ) + { + token.Length++; + token.Flags |= TF_Assign; + // token.Flags |= TokFlags::Assignment; + // token.Type = TokType::Assign_L_Or; + + if ( left ) + move_forward(); + } + else + while ( left && current == *( scanner - 1 ) && token.Length < 3 ) + { + token.Length++; + + if ( left ) + move_forward(); + } + goto FoundToken; + } + + // Dash is unfortunatlly a bit more complicated... + case '-' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Operator; + // token.Type = TokType::Subtract; + token.Flags = TF_Operator; + if ( left ) + { + move_forward(); + + if ( current == '>' ) + { + token.Length++; + // token.Type = TokType::Access_PointerToMemberSymbol; + token.Flags |= TF_AccessOperator; + move_forward(); + + if ( current == '*' ) + { + // token.Type = TokType::Access_PointerToMemberOfPointerSymbol; + token.Length++; + move_forward(); + } + } + else if ( current == '=' ) + { + token.Length++; + // token.Type = TokType::Assign_Subtract; + token.Flags |= TF_Assign; + + if ( left ) + move_forward(); + } + else + while ( left && current == *( scanner - 1 ) && token.Length < 3 ) + { + token.Length++; + + if ( left ) + move_forward(); + } + } + goto FoundToken; + } + case '/' : + { + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Operator; + // token.Type = TokType::Divide; + token.Flags = TF_Operator; + move_forward(); + + if ( left ) + { + if ( current == '=' ) + { + // token.Type = TokeType::Assign_Divide; + move_forward(); + token.Length++; + token.Flags = TF_Assign; + } + else if ( current == '/' ) + { + token.Type = TokType::Comment; + token.Length = 2; + token.Flags = TF_Null; + move_forward(); + + while ( left && current != '\n' && current != '\r' ) + { + move_forward(); + token.Length++; + } + + if ( current == '\r' ) + { + move_forward(); + token.Length++; + } + if ( current == '\n' ) + { + move_forward(); + token.Length++; + } + Tokens.append( token ); + continue; + } + else if ( current == '*' ) + { + token.Type = TokType::Comment; + token.Length = 2; + token.Flags = TF_Null; + move_forward(); + + bool star = current == '*'; + bool slash = scanner[1] == '/'; + bool at_end = star && slash; + while ( left && ! at_end ) + { + move_forward(); + token.Length++; + + star = current == '*'; + slash = scanner[1] == '/'; + at_end = star && slash; + } + token.Length += 2; + move_forward(); + move_forward(); + + if ( current == '\r' ) + { + move_forward(); + token.Length++; + } + if ( current == '\n' ) + { + move_forward(); + token.Length++; + } + Tokens.append( token ); + // end_line(); + continue; + } + } + goto FoundToken; + } + } + + if ( char_is_alpha( current ) || current == '_' ) + { + token.Text = scanner; + token.Length = 1; + move_forward(); + + while ( left && ( char_is_alphanumeric( current ) || current == '_' ) ) + { + move_forward(); + token.Length++; + } + + goto FoundToken; + } + else if ( char_is_digit( current ) ) + { + // This is a very brute force lex, no checks are done for validity of literal. + + token.Text = scanner; + token.Length = 1; + token.Type = TokType::Number; + token.Flags = TF_Literal; + move_forward(); + + if ( left && ( current == 'x' || current == 'X' || current == 'b' || current == 'B' || current == 'o' || current == 'O' ) ) + { + move_forward(); + token.Length++; + + while ( left && char_is_hex_digit( current ) ) + { + move_forward(); + token.Length++; + } + + goto FoundToken; + } + + while ( left && char_is_digit( current ) ) + { + move_forward(); + token.Length++; + } + + if ( left && current == '.' ) + { + move_forward(); + token.Length++; + + while ( left && char_is_digit( current ) ) + { + move_forward(); + token.Length++; + } + } + + goto FoundToken; + } + else + { + s32 start = max( 0, Tokens.num() - 100 ); + log_fmt( "\n%d\n", start ); + for ( s32 idx = start; idx < Tokens.num(); idx++ ) + { + log_fmt( "Token %d Type: %s : %.*s\n", idx, ETokType::to_str( Tokens[idx].Type ).Ptr, Tokens[idx].Length, Tokens[idx].Text ); + } + + String context_str = String::fmt_buf( GlobalAllocator, "%.*s", min( 100, left ), scanner ); + log_failure( "Failed to lex token '%c' (%d, %d)\n%s", current, line, column, context_str ); + + // Skip to next whitespace since we can't know if anything else is valid until then. + while ( left && ! char_is_space( current ) ) + { + move_forward(); + } + } + + FoundToken: + lex_found_token( content, left, scanner, line, column, defines, token ); + } + + if ( Tokens.num() == 0 ) + { + log_failure( "Failed to lex any tokens" ); + return { { nullptr }, 0 }; + } + + defines.clear(); + // defines_map_arena.free(); + return { Tokens, 0 }; + } + +#undef current +#undef move_forward +#undef SkipWhitespace + + // namespace parser +} // namespace parser + +namespace parser +{ + + // TODO(Ed) : Rename ETokType::Capture_Start, ETokType::Capture_End to Open_Parenthesis adn Close_Parenthesis + + constexpr bool dont_skip_formatting = false; + + struct StackNode + { + StackNode* Prev; + + Token Start; + Token Name; // The name of the AST node (if parsed) + StrC ProcName; // The name of the procedure + }; + + struct ParseContext + { + TokArray Tokens; + StackNode* Scope; + + void push( StackNode* node ) + { + node->Prev = Scope; + Scope = node; + +#if 0 && Build_Debug + log_fmt("\tEntering Context: %.*s\n", Scope->ProcName.Len, Scope->ProcName.Ptr ); +#endif + } + + void pop() + { +#if 0 && Build_Debug + log_fmt("\tPopping Context: %.*s\n", Scope->ProcName.Len, Scope->ProcName.Ptr ); +#endif + Scope = Scope->Prev; + } + + String to_string() + { + String result = String::make_reserve( GlobalAllocator, kilobytes( 4 ) ); + + Token scope_start = Scope->Start; + Token last_valid = Tokens.Idx >= Tokens.Arr.num() ? Tokens.Arr[Tokens.Arr.num() - 1] : Tokens.current(); + + sptr length = scope_start.Length; + char const* current = scope_start.Text + length; + while ( current <= Tokens.Arr.back().Text && *current != '\n' && length < 74 ) + { + current++; + length++; + } + + String line = String::make( GlobalAllocator, { length, scope_start.Text } ); + result.append_fmt( "\tScope : %s\n", line ); + line.free(); + + sptr dist = (sptr)last_valid.Text - (sptr)scope_start.Text + 2; + sptr length_from_err = dist; + String line_from_err = String::make( GlobalAllocator, { length_from_err, last_valid.Text } ); + + if ( length_from_err < 100 ) + result.append_fmt( "\t(%d, %d):%*c\n", last_valid.Line, last_valid.Column, length_from_err, '^' ); + else + result.append_fmt( "\t(%d, %d)\n", last_valid.Line, last_valid.Column ); + + StackNode* curr_scope = Scope; + s32 level = 0; + do + { + if ( curr_scope->Name ) + { + result.append_fmt( "\t%d: %s, AST Name: %.*s\n", level, curr_scope->ProcName.Ptr, curr_scope->Name.Length, curr_scope->Name.Text ); + } + else + { + result.append_fmt( "\t%d: %s\n", level, curr_scope->ProcName.Ptr ); + } + + curr_scope = curr_scope->Prev; + level++; + } while ( curr_scope ); + return result; + } + }; + + global ParseContext Context; + + bool TokArray::__eat( TokType type ) + { + if ( Arr.num() - Idx <= 0 ) + { + log_failure( "No tokens left.\n%s", Context.to_string() ); + return false; + } + + if ( ( Arr[Idx].Type == TokType::NewLine && type != TokType::NewLine ) || ( Arr[Idx].Type == TokType::Comment && type != TokType::Comment ) ) + { + Idx++; + } + + if ( Arr[Idx].Type != type ) + { + log_failure( + "Parse Error, TokArray::eat, Expected: ' %s ' not ' %.*s ' (%d, %d)`\n%s", + ETokType::to_str( type ).Ptr, + Arr[Idx].Length, + Arr[Idx].Text, + current().Line, + current().Column, + Context.to_string() + ); + + return false; + } + +#if 0 && Build_Debug + log_fmt("Ate: %S\n", Arr[Idx].to_string() ); +#endif + + Idx++; + return true; + } + + internal void init() + { + Tokens = Array::init_reserve( LexArena, ( LexAllocator_Size - sizeof( Array::Header ) ) / sizeof( Token ) ); + + defines_map_arena = Arena_256KB::init(); + defines = HashTable::init_reserve( defines_map_arena, 256 ); + } + + internal void deinit() + { + parser::Tokens = { nullptr }; + } + +#pragma region Helper Macros + +#define check_parse_args( def ) \ + if ( def.Len <= 0 ) \ + { \ + log_failure( "gen::" stringize( __func__ ) ": length must greater than 0" ); \ + parser::Context.pop(); \ + return CodeInvalid; \ + } \ + if ( def.Ptr == nullptr ) \ + { \ + log_failure( "gen::" stringize( __func__ ) ": def was null" ); \ + parser::Context.pop(); \ + return CodeInvalid; \ + } + +#define currtok_noskip Context.Tokens.current( dont_skip_formatting ) +#define currtok Context.Tokens.current() +#define prevtok Context.Tokens.previous() +#define nexttok Context.Tokens.next() +#define eat( Type_ ) Context.Tokens.__eat( Type_ ) +#define left ( Context.Tokens.Arr.num() - Context.Tokens.Idx ) + +#define check_noskip( Type_ ) ( left && currtok_noskip.Type == Type_ ) +#define check( Type_ ) ( left && currtok.Type == Type_ ) + +#define push_scope() \ + StackNode scope { nullptr, currtok_noskip, NullToken, txt( __func__ ) }; \ + Context.push( &scope ) + +#pragma endregion Helper Macros + + // Procedure Forwards ( Entire parser internal parser interface ) + + internal Code parse_array_decl(); + internal CodeAttributes parse_attributes(); + internal CodeComment parse_comment(); + internal Code parse_complicated_definition( TokType which ); + internal CodeBody parse_class_struct_body( TokType which, Token name = NullToken ); + internal Code parse_class_struct( TokType which, bool inplace_def ); + internal CodeDefine parse_define(); + internal Code parse_expression(); + 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 Code parse_function_body(); + internal Code parse_global_nspace(); + internal Code parse_global_nspace_constructor_destructor( CodeSpecifiers specifiers ); + internal Token parse_identifier( bool* possible_member_function = nullptr ); + internal CodeInclude parse_include(); + internal CodeOperator parse_operator_after_ret_type( ModuleFlag mflags, CodeAttributes attributes, CodeSpecifiers specifiers, CodeType ret_type ); + internal Code parse_operator_function_or_variable( bool expects_function, CodeAttributes attributes, CodeSpecifiers specifiers ); + internal CodePragma parse_pragma(); + internal CodeParam parse_params( bool use_template_capture = false ); + internal CodePreprocessCond parse_preprocess_cond(); + internal Code parse_simple_preprocess( TokType which ); + internal Code parse_static_assert(); + internal void parse_template_args( Token& token ); + internal CodeVar parse_variable_after_name( ModuleFlag mflags, CodeAttributes attributes, CodeSpecifiers specifiers, CodeType type, StrC name ); + internal CodeVar parse_variable_declaration_list(); + + internal CodeClass parse_class( bool inplace_def = false ); + internal CodeConstructor parse_constructor( CodeSpecifiers specifiers ); + internal CodeDestructor parse_destructor( CodeSpecifiers specifiers = NoCode ); + internal CodeEnum parse_enum( bool inplace_def = false ); + internal CodeBody parse_export_body(); + internal CodeBody parse_extern_link_body(); + internal CodeExtern parse_extern_link(); + internal CodeFriend parse_friend(); + internal CodeFn parse_function(); + internal CodeNS parse_namespace(); + internal CodeOpCast parse_operator_cast( CodeSpecifiers specifiers = NoCode ); + internal CodeStruct parse_struct( bool inplace_def = false ); + internal CodeVar parse_variable(); + internal CodeTemplate parse_template(); + internal CodeType parse_type( bool from_template = false, bool* is_function = nullptr ); + internal CodeTypedef parse_typedef(); + internal CodeUnion parse_union( bool inplace_def = false ); + internal CodeUsing parse_using(); + + constexpr bool inplace_def = true; + + // Internal parsing functions + + constexpr bool strip_formatting_dont_preserve_newlines = false; + + /* + This function was an attempt at stripping formatting from any c++ code. + It has edge case failures that prevent it from being used in function bodies. + */ + internal String strip_formatting( StrC raw_text, bool preserve_newlines = true ) + { + String content = String::make_reserve( GlobalAllocator, raw_text.Len ); + + if ( raw_text.Len == 0 ) + return content; + +#define cut_length ( scanner - raw_text.Ptr - last_cut ) +#define cut_ptr ( raw_text.Ptr + last_cut ) +#define pos ( sptr( scanner ) - sptr( raw_text.Ptr ) ) +#define move_fwd() \ + do \ + { \ + scanner++; \ + tokleft--; \ + } while ( 0 ) + + s32 tokleft = raw_text.Len; + sptr last_cut = 0; + char const* scanner = raw_text.Ptr; + + if ( scanner[0] == ' ' ) + { + move_fwd(); + last_cut = 1; + } + + bool within_string = false; + bool within_char = false; + bool must_keep_newline = false; + while ( tokleft ) + { + // Skip over the content of string literals + if ( scanner[0] == '"' ) + { + move_fwd(); + + while ( tokleft && ( scanner[0] != '"' || *( scanner - 1 ) == '\\' ) ) + { + if ( scanner[0] == '\\' && tokleft > 1 ) + { + scanner += 2; + tokleft -= 2; + } + else + { + move_fwd(); + } + } + + // Skip the closing " + if ( tokleft ) + move_fwd(); + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Skip over the content of character literals + if ( scanner[0] == '\'' ) + { + move_fwd(); + + while ( tokleft && ( scanner[0] != '\'' || ( *( scanner - 1 ) == '\\' ) ) ) + { + move_fwd(); + } + + // Skip the closing ' + if ( tokleft ) + move_fwd(); + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Block comments + if ( tokleft > 1 && scanner[0] == '/' && scanner[1] == '*' ) + { + while ( tokleft > 1 && ! ( scanner[0] == '*' && scanner[1] == '/' ) ) + move_fwd(); + + scanner += 2; + tokleft -= 2; + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Line comments + if ( tokleft > 1 && scanner[0] == '/' && scanner[1] == '/' ) + { + must_keep_newline = true; + + scanner += 2; + tokleft -= 2; + + while ( tokleft && scanner[0] != '\n' ) + move_fwd(); + + if ( tokleft ) + move_fwd(); + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Tabs + if ( scanner[0] == '\t' ) + { + if ( pos > last_cut ) + content.append( cut_ptr, cut_length ); + + if ( content.back() != ' ' ) + content.append( ' ' ); + + move_fwd(); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + if ( tokleft > 1 && scanner[0] == '\r' && scanner[1] == '\n' ) + { + if ( must_keep_newline || preserve_newlines ) + { + must_keep_newline = false; + + scanner += 2; + tokleft -= 2; + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + if ( pos > last_cut ) + content.append( cut_ptr, cut_length ); + + // Replace with a space + if ( content.back() != ' ' ) + content.append( ' ' ); + + scanner += 2; + tokleft -= 2; + + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + if ( scanner[0] == '\n' ) + { + if ( must_keep_newline || preserve_newlines ) + { + must_keep_newline = false; + + move_fwd(); + + content.append( cut_ptr, cut_length ); + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + if ( pos > last_cut ) + content.append( cut_ptr, cut_length ); + + // Replace with a space + if ( content.back() != ' ' ) + content.append( ' ' ); + + move_fwd(); + + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Escaped newlines + if ( scanner[0] == '\\' ) + { + content.append( cut_ptr, cut_length ); + + s32 amount_to_skip = 1; + if ( tokleft > 1 && scanner[1] == '\n' ) + { + amount_to_skip = 2; + } + else if ( tokleft > 2 && scanner[1] == '\r' && scanner[2] == '\n' ) + { + amount_to_skip = 3; + } + + if ( amount_to_skip > 1 && pos == last_cut ) + { + scanner += amount_to_skip; + tokleft -= amount_to_skip; + } + else + move_fwd(); + + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + continue; + } + + // Consectuive spaces + if ( tokleft > 1 && char_is_space( scanner[0] ) && char_is_space( scanner[1] ) ) + { + content.append( cut_ptr, cut_length ); + do + { + move_fwd(); + } while ( tokleft && char_is_space( scanner[0] ) ); + + last_cut = sptr( scanner ) - sptr( raw_text.Ptr ); + + // Preserve only 1 space of formattting + if ( content.back() != ' ' ) + content.append( ' ' ); + + continue; + } + + move_fwd(); + } + + if ( last_cut < raw_text.Len ) + { + content.append( cut_ptr, raw_text.Len - last_cut ); + } + +#undef cut_ptr +#undef cut_length +#undef pos +#undef move_fwd + + return content; + } + + internal Code parse_array_decl() + { + push_scope(); + + if ( check( TokType::Operator ) && currtok.Text[0] == '[' && currtok.Text[1] == ']' ) + { + Code array_expr = untyped_str( currtok ); + eat( TokType::Operator ); + // [] + + Context.pop(); + return array_expr; + } + + if ( check( TokType::BraceSquare_Open ) ) + { + eat( TokType::BraceSquare_Open ); + // [ + + if ( left == 0 ) + { + log_failure( "Error, unexpected end of array declaration ( '[]' scope started )\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + if ( currtok.Type == TokType::BraceSquare_Close ) + { + log_failure( "Error, empty array expression in definition\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + Token untyped_tok = currtok; + + while ( left && currtok.Type != TokType::BraceSquare_Close ) + { + eat( currtok.Type ); + } + + untyped_tok.Length = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)untyped_tok.Text; + + Code array_expr = untyped_str( untyped_tok ); + // [ + + if ( left == 0 ) + { + log_failure( "Error, unexpected end of array declaration, expected ]\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + if ( currtok.Type != TokType::BraceSquare_Close ) + { + log_failure( "%s: Error, expected ] in array declaration, not %s\n%s", ETokType::to_str( currtok.Type ), Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + eat( TokType::BraceSquare_Close ); + // [ ] + + // Its a multi-dimensional array + if ( check( TokType::BraceSquare_Open ) ) + { + Code adjacent_arr_expr = parse_array_decl(); + // [ ][ ]... + + array_expr->Next = adjacent_arr_expr.ast; + } + + Context.pop(); + return array_expr; + } + + Context.pop(); + return { nullptr }; + } + + internal inline CodeAttributes parse_attributes() + { + push_scope(); + + Token start = currtok; + s32 len = 0; + + // There can be more than one attribute. If there is flatten them to a single string. + // TODO(Ed): Support keeping an linked list of attributes similar to parameters + while ( left && currtok.is_attribute() ) + { + if ( check( TokType::Attribute_Open ) ) + { + eat( TokType::Attribute_Open ); + // [[ + + while ( left && currtok.Type != TokType::Attribute_Close ) + { + eat( currtok.Type ); + } + // [[ + + eat( TokType::Attribute_Close ); + // [[ ]] + + len = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)start.Text; + } + else if ( check( TokType::Decl_GNU_Attribute ) ) + { + eat( TokType::Decl_GNU_Attribute ); + eat( TokType::Capture_Start ); + eat( TokType::Capture_Start ); + // __attribute__(( + + while ( left && currtok.Type != TokType::Capture_End ) + { + eat( currtok.Type ); + } + // __attribute__(( + + eat( TokType::Capture_End ); + eat( TokType::Capture_End ); + // __attribute__(( )) + + len = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)start.Text; + } + else if ( check( TokType::Decl_MSVC_Attribute ) ) + { + eat( TokType::Decl_MSVC_Attribute ); + eat( TokType::Capture_Start ); + // __declspec( + + while ( left && currtok.Type != TokType::Capture_End ) + { + eat( currtok.Type ); + } + // __declspec( + + eat( TokType::Capture_End ); + // __declspec( ) + + len = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)start.Text; + } + else if ( currtok.is_attribute() ) + { + eat( currtok.Type ); + // + + // If its a macro based attribute, this could be a functional macro such as Unreal's UE_DEPRECATED(...) + if ( check( TokType::Capture_Start ) ) + { + eat( TokType::Capture_Start ); + + s32 level = 0; + while ( left && currtok.Type != TokType::Capture_End && level == 0 ) + { + if ( currtok.Type == TokType::Capture_Start ) + ++level; + if ( currtok.Type == TokType::Capture_End ) + --level; + eat( currtok.Type ); + } + eat( TokType::Capture_End ); + } + + len = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)start.Text; + // ( ... ) + } + } + + if ( len > 0 ) + { + StrC attribute_txt = { len, start.Text }; + Context.pop(); + + String name_stripped = strip_formatting( attribute_txt, strip_formatting_dont_preserve_newlines ); + + Code result = make_code(); + result->Type = ECode::PlatformAttributes; + result->Name = get_cached_string( name_stripped ); + result->Content = result->Name; + // result->Token = + + return (CodeAttributes)result; + } + + Context.pop(); + return { nullptr }; + } + + internal Code parse_class_struct( TokType which, bool binplace_def = false ) + { + if ( which != TokType::Decl_Class && which != TokType::Decl_Struct ) + { + log_failure( "Error, expected class or struct, not %s\n%s", ETokType::to_str( which ), Context.to_string() ); + return CodeInvalid; + } + + Token name { nullptr, 0, TokType::Invalid }; + + AccessSpec access = AccessSpec::Default; + CodeType parent = { nullptr }; + CodeBody body = { nullptr }; + CodeAttributes attributes = { nullptr }; + ModuleFlag mflags = ModuleFlag::None; + + CodeClass result = CodeInvalid; + + if ( check( TokType::Module_Export ) ) + { + mflags = ModuleFlag::Export; + eat( TokType::Module_Export ); + } + // + + eat( which ); + // + + attributes = parse_attributes(); + // + + if ( check( TokType::Identifier ) ) + { + name = parse_identifier(); + Context.Scope->Name = name; + } + // + + local_persist char interface_arr_mem[kilobytes( 4 )] { 0 }; + Array interfaces = Array::init_reserve( Arena::init_from_memory( interface_arr_mem, kilobytes( 4 ) ), 4 ); + + // TODO(Ed) : Make an AST_DerivedType, we'll store any arbitary derived type into there as a linear linked list of them. + if ( check( TokType::Assign_Classifer ) ) + { + eat( TokType::Assign_Classifer ); + // : + + if ( currtok.is_access_specifier() ) + { + access = currtok.to_access_specifier(); + // : + eat( currtok.Type ); + } + + Token parent_tok = parse_identifier(); + parent = def_type( parent_tok ); + // : + + while ( check( TokType::Comma ) ) + { + eat( TokType::Comma ); + // : , + + if ( currtok.is_access_specifier() ) + { + eat( currtok.Type ); + } + Token interface_tok = parse_identifier(); + + interfaces.append( def_type( interface_tok ) ); + // : , ... + } + } + + if ( check( TokType::BraceCurly_Open ) ) + { + body = parse_class_struct_body( which, name ); + } + // : , ... { } + + CodeComment inline_cmt = NoCode; + if ( ! binplace_def ) + { + Token stmt_end = currtok; + eat( TokType::Statement_End ); + // : , ... { }; + + if ( currtok_noskip.Type == TokType::Comment && currtok_noskip.Line == stmt_end.Line ) + inline_cmt = parse_comment(); + // : , ... { }; + } + + if ( which == TokType::Decl_Class ) + result = def_class( name, body, parent, access, attributes, mflags ); + + else + result = def_struct( name, body, (CodeType)parent, access, attributes, mflags ); + + if ( inline_cmt ) + result->InlineCmt = inline_cmt; + + interfaces.free(); + return result; + } + + internal neverinline CodeBody parse_class_struct_body( TokType which, Token name ) + { + using namespace ECode; + push_scope(); + + eat( TokType::BraceCurly_Open ); + // { + + CodeBody result = (CodeBody)make_code(); + + if ( which == TokType::Decl_Class ) + result->Type = Class_Body; + + else + result->Type = Struct_Body; + + while ( left && currtok_noskip.Type != TokType::BraceCurly_Close ) + { + Code member = Code::Invalid; + CodeAttributes attributes = { nullptr }; + CodeSpecifiers specifiers = { nullptr }; + + bool expects_function = false; + + // Context.Scope->Start = currtok_noskip; + + if ( currtok_noskip.Type == TokType::Preprocess_Hash ) + eat( TokType::Preprocess_Hash ); + + 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 : + member = fmt_newline; + eat( TokType::NewLine ); + break; + + case TokType::Comment : + member = parse_comment(); + break; + + case TokType::Access_Public : + member = access_public; + eat( TokType::Access_Public ); + eat( TokType::Assign_Classifer ); + // public: + break; + + case TokType::Access_Protected : + member = access_protected; + eat( TokType::Access_Protected ); + eat( TokType::Assign_Classifer ); + // protected: + break; + + case TokType::Access_Private : + member = access_private; + eat( TokType::Access_Private ); + eat( TokType::Assign_Classifer ); + // private: + break; + + case TokType::Decl_Class : + member = parse_complicated_definition( TokType::Decl_Class ); + // class + break; + + case TokType::Decl_Enum : + member = parse_complicated_definition( TokType::Decl_Enum ); + // enum + break; + + case TokType::Decl_Friend : + member = parse_friend(); + // friend + break; + + case TokType::Decl_Operator : + member = parse_operator_cast(); + // operator () + break; + + case TokType::Decl_Struct : + member = parse_complicated_definition( TokType::Decl_Struct ); + // struct + break; + + case TokType::Decl_Template : + member = parse_template(); + // template< ... > + break; + + case TokType::Decl_Typedef : + member = parse_typedef(); + // typedef + break; + + case TokType::Decl_Union : + member = parse_complicated_definition( TokType::Decl_Union ); + // union + break; + + case TokType::Decl_Using : + member = parse_using(); + // using + break; + + case TokType::Operator : + if ( currtok.Text[0] != '~' ) + { + log_failure( "Operator token found in global body but not destructor unary negation\n%s", Context.to_string() ); + return CodeInvalid; + } + + member = parse_destructor(); + // ~() + break; + + case TokType::Preprocess_Define : + member = parse_define(); + // #define + break; + + case TokType::Preprocess_Include : + member = parse_include(); + // #include + break; + + case TokType::Preprocess_If : + case TokType::Preprocess_IfDef : + case TokType::Preprocess_IfNotDef : + case TokType::Preprocess_ElIf : + member = parse_preprocess_cond(); + // # + break; + + case TokType::Preprocess_Else : + member = preprocess_else; + eat( TokType::Preprocess_Else ); + // #else + break; + + case TokType::Preprocess_EndIf : + member = preprocess_endif; + eat( TokType::Preprocess_EndIf ); + // #endif + break; + + case TokType::Preprocess_Macro : + member = parse_simple_preprocess( TokType::Preprocess_Macro ); + // + break; + + case TokType::Preprocess_Pragma : + member = parse_pragma(); + // #pragma + break; + + case TokType::Preprocess_Unsupported : + member = parse_simple_preprocess( TokType::Preprocess_Unsupported ); + // # + break; + + case TokType::StaticAssert : + member = parse_static_assert(); + // static_assert + break; + + case TokType::Attribute_Open : + case TokType::Decl_GNU_Attribute : + case TokType::Decl_MSVC_Attribute : +#define Entry( attribute, str ) case TokType::attribute : + GEN_DEFINE_ATTRIBUTE_TOKENS +#undef Entry + { + attributes = parse_attributes(); + // + } + //! Fallthrough intended + case TokType::Spec_Consteval : + case TokType::Spec_Constexpr : + case TokType::Spec_Constinit : + case TokType::Spec_Explicit : + case TokType::Spec_ForceInline : + case TokType::Spec_Inline : + case TokType::Spec_Mutable : + case TokType::Spec_NeverInline : + case TokType::Spec_Static : + case TokType::Spec_Volatile : + case TokType::Spec_Virtual : + { + 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::Constinit : + case ESpecifier::Explicit : + case ESpecifier::Inline : + case ESpecifier::ForceInline : + case ESpecifier::Mutable : + case ESpecifier::NeverInline : + case ESpecifier::Static : + case ESpecifier::Volatile : + case ESpecifier::Virtual : + break; + + case ESpecifier::Consteval : + expects_function = true; + 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 ); + } + // + + if ( currtok.is_attribute() ) + { + // Unfortuantely Unreal has code where there is attirbutes before specifiers + CodeAttributes more_attributes = parse_attributes(); + + if ( attributes ) + { + String fused = String::make_reserve( GlobalAllocator, attributes->Content.length() + more_attributes->Content.length() ); + fused.append_fmt( "%S %S", attributes->Content, more_attributes->Content ); + + attributes->Name = get_cached_string( fused ); + attributes->Content = attributes->Name; + // + } + + attributes = more_attributes; + } + + if ( currtok.Type == TokType::Operator && currtok.Text[0] == '~' ) + { + member = parse_destructor( specifiers ); + // ~() + break; + } + + if ( currtok.Type == TokType::Decl_Operator ) + { + member = parse_operator_cast( specifiers ); + // operator () + break; + } + } + //! Fallthrough intentional + case TokType::Identifier : + case TokType::Spec_Const : + case TokType::Type_Unsigned : + case TokType::Type_Signed : + case TokType::Type_Short : + case TokType::Type_Long : + case TokType::Type_bool : + case TokType::Type_char : + case TokType::Type_int : + case TokType::Type_double : + { + if ( nexttok.Type == TokType::Capture_Start && name.Length && currtok.Type == TokType::Identifier ) + { + if ( str_compare( name.Text, currtok.Text, name.Length ) == 0 ) + { + member = parse_constructor( specifiers ); + // () + break; + } + } + + member = parse_operator_function_or_variable( expects_function, attributes, specifiers ); + // operator ... + // or + // ... + } + break; + + default : + Token untyped_tok = currtok; + + while ( left && currtok.Type != TokType::BraceCurly_Close ) + { + untyped_tok.Length = ( (sptr)currtok.Text + currtok.Length ) - (sptr)untyped_tok.Text; + eat( currtok.Type ); + } + + member = untyped_str( untyped_tok ); + // Something unknown + break; + } + + if ( member == Code::Invalid ) + { + log_failure( "Failed to parse member\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + result.append( member ); + } + + eat( TokType::BraceCurly_Close ); + // { } + Context.pop(); + return result; + } + + internal CodeComment parse_comment() + { + StackNode scope { nullptr, currtok_noskip, NullToken, txt( __func__ ) }; + Context.push( &scope ); + + CodeComment result = (CodeComment)make_code(); + result->Type = ECode::Comment; + result->Content = get_cached_string( currtok_noskip ); + result->Name = result->Content; + // result->Token = currtok_noskip; + eat( TokType::Comment ); + + Context.pop(); + return result; + } + + internal Code parse_complicated_definition( TokType which ) + { + push_scope(); + + bool is_inplace = false; + + TokArray tokens = Context.Tokens; + + s32 idx = tokens.Idx; + s32 level = 0; + for ( ; idx < tokens.Arr.num(); idx++ ) + { + if ( tokens[idx].Type == TokType::BraceCurly_Open ) + level++; + + if ( tokens[idx].Type == TokType::BraceCurly_Close ) + level--; + + if ( level == 0 && tokens[idx].Type == TokType::Statement_End ) + break; + } + + if ( ( idx - 2 ) == tokens.Idx ) + { + // Its a forward declaration only + Code result = parse_forward_or_definition( which, is_inplace ); + // ; + Context.pop(); + return result; + } + + Token tok = tokens[idx - 1]; + if ( tok.is_specifier() && is_trailing( ESpecifier::to_type( tok ) ) ) + { + // (...) ...; + + 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 } ); + // , 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 ) + { + tok = tokens[idx - 2]; + bool is_indirection = tok.Type == TokType::Ampersand || tok.Type == TokType::Star; + bool ok_to_parse = false; + + if ( tok.Type == TokType::BraceCurly_Close ) + { + // Its an inplace definition + // { ... } ; + ok_to_parse = true; + is_inplace = true; + } + else if ( tok.Type == TokType::Identifier && tokens[idx - 3].Type == which ) + { + // Its a variable with type ID using namespace. + // ; + ok_to_parse = true; + } + else if ( tok.Type == TokType::Assign_Classifer + && ( ( tokens[idx - 5].Type == which && tokens[idx - 4].Type == TokType::Decl_Class ) || ( tokens[idx - 4].Type == which ) ) ) + { + // Its a forward declaration of an enum + // : ; + // : ; + ok_to_parse = true; + Code result = parse_enum(); + Context.pop(); + return result; + } + else if ( is_indirection ) + { + // Its a indirection type with type ID using struct namespace. + // * ; + ok_to_parse = true; + } + + if ( ! ok_to_parse ) + { + log_failure( "Unsupported or bad member definition after %s declaration\n%s", to_str( which ), Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + Code result = parse_operator_function_or_variable( false, { nullptr }, { nullptr } ); + // , or Name> ... + Context.pop(); + return result; + } + else if ( tok.Type >= TokType::Type_Unsigned && tok.Type <= TokType::Type_MS_W64 ) + { + tok = tokens[idx - 2]; + + if ( tok.Type != TokType::Assign_Classifer + || ( ( tokens[idx - 5].Type != which && tokens[idx - 4].Type != TokType::Decl_Class ) && ( tokens[idx - 4].Type != which ) ) ) + { + log_failure( "Unsupported or bad member definition after %s declaration\n%s", to_str( which ), Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + // Its a forward declaration of an enum class + // : ; + // : ; + Code result = parse_enum(); + Context.pop(); + return result; + } + else if ( tok.Type == TokType::BraceCurly_Close ) + { + // Its a definition + Code result = parse_forward_or_definition( which, is_inplace ); + // { ... }; + Context.pop(); + return result; + } + else if ( tok.Type == TokType::BraceSquare_Close ) + { + // Its an array definition + Code result = parse_operator_function_or_variable( false, { nullptr }, { nullptr } ); + // [ ... ]; + Context.pop(); + return result; + } + else + { + log_failure( "Unsupported or bad member definition after %s declaration\n%S", to_str( which ).Ptr, Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + } + + internal inline CodeDefine parse_define() + { + push_scope(); + eat( TokType::Preprocess_Define ); + // #define + + CodeDefine define = (CodeDefine)make_code(); + define->Type = ECode::Preprocess_Define; + + if ( ! check( TokType::Identifier ) ) + { + log_failure( "Error, expected identifier after #define\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + Context.Scope->Name = currtok; + define->Name = get_cached_string( currtok ); + eat( TokType::Identifier ); + // #define + + if ( ! check( TokType::Preprocess_Content ) ) + { + log_failure( "Error, expected content after #define %s\n%s", define->Name, Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + if ( currtok.Length == 0 ) + { + define->Content = get_cached_string( currtok ); + eat( TokType::Preprocess_Content ); + // #define + + Context.pop(); + return define; + } + + define->Content = get_cached_string( strip_formatting( currtok, strip_formatting_dont_preserve_newlines ) ); + eat( TokType::Preprocess_Content ); + // #define + + Context.pop(); + return define; + } + + internal inline Code parse_assignment_expression() + { + Code expr = { nullptr }; + + eat( TokType::Operator ); + // = + + 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 ); + // = + return expr; + } + + internal inline Code parse_forward_or_definition( TokType which, bool is_inplace ) + { + Code result = CodeInvalid; + + switch ( which ) + { + case TokType::Decl_Class : + result = parse_class( is_inplace ); + return result; + + case TokType::Decl_Enum : + result = parse_enum( is_inplace ); + return result; + + case TokType::Decl_Struct : + result = parse_struct( is_inplace ); + return result; + + case TokType::Decl_Union : + result = parse_union( is_inplace ); + return result; + + default : + log_failure( + "Error, wrong token type given to parse_complicated_definition " + "(only supports class, enum, struct, union) \n%s", + Context.to_string() + ); + + return CodeInvalid; + } + } + + // Function parsing is handled in multiple places because its initial signature is shared with variable parsing + internal inline CodeFn parse_function_after_name( ModuleFlag mflags, CodeAttributes attributes, CodeSpecifiers specifiers, CodeType ret_type, Token name ) + { + push_scope(); + CodeParam params = parse_params(); + // ( ) + + // TODO(Ed), Review old comment : These have to be kept separate from the return type's specifiers. + while ( left && currtok.is_specifier() ) + { + if ( specifiers.ast == nullptr ) + { + specifiers = def_specifier( ESpecifier::to_type( currtok ) ); + eat( currtok.Type ); + continue; + } + + specifiers.append( ESpecifier::to_type( currtok ) ); + eat( currtok.Type ); + } + // ( ) + + CodeBody body = NoCode; + CodeComment inline_cmt = NoCode; + if ( check( TokType::BraceCurly_Open ) ) + { + body = parse_function_body(); + if ( body == Code::Invalid ) + { + Context.pop(); + return CodeInvalid; + } + // ( ) { } + } + else if ( check( TokType::Operator ) && currtok.Text[0] == '=' ) + { + eat( TokType::Operator ); + specifiers.append( ESpecifier::Pure ); + + eat( TokType::Number ); + Token stmt_end = currtok; + eat( TokType::Statement_End ); + // ( ) = 0; + + if ( currtok_noskip.Type == TokType::Comment && currtok_noskip.Line == stmt_end.Line ) + inline_cmt = parse_comment(); + // ( ) ; + } + else + { + Token stmt_end = currtok; + eat( TokType::Statement_End ); + // ( ) ; + + if ( currtok_noskip.Type == TokType::Comment && currtok_noskip.Line == stmt_end.Line ) + inline_cmt = parse_comment(); + // ( ) ; + } + + using namespace ECode; + + String name_stripped = String::make( GlobalAllocator, name ); + name_stripped.strip_space(); + + CodeFn result = (CodeFn)make_code(); + result->Name = get_cached_string( name_stripped ); + result->ModuleFlags = mflags; + + if ( body ) + { + switch ( body->Type ) + { + case Function_Body : + case Untyped : + break; + + default : + { + log_failure( "Body must be either of Function_Body or Untyped type, %s\n%s", body.debug_str(), Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + } + + result->Type = Function; + result->Body = body; + } + else + { + result->Type = Function_Fwd; + } + + if ( attributes ) + result->Attributes = attributes; + + if ( specifiers ) + result->Specs = specifiers; + + result->ReturnType = ret_type; + + if ( params ) + result->Params = params; + + if ( inline_cmt ) + result->InlineCmt = inline_cmt; + + Context.pop(); + return result; + } + + internal Code parse_function_body() + { + using namespace ECode; + push_scope(); + + eat( TokType::BraceCurly_Open ); + + CodeBody result = (CodeBody)make_code(); + result->Type = Function_Body; + + // TODO : Support actual parsing of function body + Token start = currtok_noskip; + + s32 level = 0; + while ( left && ( currtok_noskip.Type != TokType::BraceCurly_Close || level > 0 ) ) + { + if ( currtok_noskip.Type == TokType::BraceCurly_Open ) + level++; + + else if ( currtok_noskip.Type == TokType::BraceCurly_Close && level > 0 ) + level--; + + eat( currtok_noskip.Type ); + } + + Token previous = prevtok; + + s32 len = ( (sptr)prevtok.Text + prevtok.Length ) - (sptr)start.Text; + + if ( len > 0 ) + { + result.append( def_execution( { len, start.Text } ) ); + } + + eat( TokType::BraceCurly_Close ); + + Context.pop(); + return result; + } + + internal neverinline CodeBody parse_global_nspace( CodeT which ) + { + using namespace ECode; + + push_scope(); + + if ( which != Namespace_Body && which != Global_Body && which != Export_Body && which != Extern_Linkage_Body ) + return CodeInvalid; + + if ( which != Global_Body ) + eat( TokType::BraceCurly_Open ); + // { + + CodeBody result = (CodeBody)make_code(); + result->Type = which; + + while ( left && currtok_noskip.Type != TokType::BraceCurly_Close ) + { + Code member = Code::Invalid; + CodeAttributes attributes = { nullptr }; + CodeSpecifiers specifiers = { nullptr }; + + bool expects_function = false; + + // Context.Scope->Start = currtok_noskip; + + if ( currtok_noskip.Type == TokType::Preprocess_Hash ) + eat( TokType::Preprocess_Hash ); + + 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 : + // Empty lines are auto skipped by Tokens.current() + member = fmt_newline; + eat( TokType::NewLine ); + break; + + case TokType::Comment : + member = parse_comment(); + break; + + case TokType::Decl_Class : + member = parse_complicated_definition( TokType::Decl_Class ); + // class + break; + + case TokType::Decl_Enum : + member = parse_complicated_definition( TokType::Decl_Enum ); + // enum + break; + + case TokType::Decl_Extern_Linkage : + if ( which == Extern_Linkage_Body ) + log_failure( "Nested extern linkage\n%s", Context.to_string() ); + + member = parse_extern_link(); + // extern "..." { ... } + break; + + case TokType::Decl_Namespace : + member = parse_namespace(); + // namespace { ... } + break; + + case TokType::Decl_Struct : + member = parse_complicated_definition( TokType::Decl_Struct ); + // struct ... + break; + + case TokType::Decl_Template : + member = parse_template(); + // template<...> ... + break; + + case TokType::Decl_Typedef : + member = parse_typedef(); + // typedef ... + break; + + case TokType::Decl_Union : + member = parse_complicated_definition( TokType::Decl_Union ); + // union ... + break; + + case TokType::Decl_Using : + member = parse_using(); + // using ... + break; + + case TokType::Preprocess_Define : + member = parse_define(); + // #define ... + break; + + case TokType::Preprocess_Include : + member = parse_include(); + // #include ... + break; + + case TokType::Preprocess_If : + case TokType::Preprocess_IfDef : + case TokType::Preprocess_IfNotDef : + case TokType::Preprocess_ElIf : + member = parse_preprocess_cond(); + // # ... + break; + + case TokType::Preprocess_Else : + member = preprocess_else; + eat( TokType::Preprocess_Else ); + // #else + break; + + case TokType::Preprocess_EndIf : + member = preprocess_endif; + eat( TokType::Preprocess_EndIf ); + // #endif + break; + + case TokType::Preprocess_Macro : + member = parse_simple_preprocess( TokType::Preprocess_Macro ); + // + break; + + case TokType::Preprocess_Pragma : + member = parse_pragma(); + // #pragma ... + break; + + case TokType::Preprocess_Unsupported : + member = parse_simple_preprocess( TokType::Preprocess_Unsupported ); + // # ... + break; + + case TokType::StaticAssert : + member = parse_static_assert(); + // static_assert( , ... ); + break; + + case TokType::Module_Export : + if ( which == Export_Body ) + log_failure( "Nested export declaration\n%s", Context.to_string() ); + + member = parse_export_body(); + // export { ... } + break; + + case TokType::Module_Import : + { + not_implemented( context ); + // import ... + } + //! Fallthrough intentional + case TokType::Attribute_Open : + case TokType::Decl_GNU_Attribute : + case TokType::Decl_MSVC_Attribute : +#define Entry( attribute, str ) case TokType::attribute : + GEN_DEFINE_ATTRIBUTE_TOKENS +#undef Entry + { + attributes = parse_attributes(); + // + } + //! Fallthrough intentional + case TokType::Spec_Consteval : + case TokType::Spec_Constexpr : + case TokType::Spec_Constinit : + case TokType::Spec_Extern : + case TokType::Spec_ForceInline : + case TokType::Spec_Global : + case TokType::Spec_Inline : + case TokType::Spec_Internal_Linkage : + case TokType::Spec_NeverInline : + case TokType::Spec_Static : + { + SpecifierT specs_found[16] { ESpecifier::NumSpecifiers }; + s32 NumSpecifiers = 0; + + while ( left && currtok.is_specifier() ) + { + SpecifierT spec = ESpecifier::to_type( currtok ); + + bool ignore_spec = false; + + switch ( spec ) + { + case ESpecifier::Constexpr : + case ESpecifier::Constinit : + case ESpecifier::ForceInline : + case ESpecifier::Global : + case ESpecifier::External_Linkage : + case ESpecifier::Internal_Linkage : + case ESpecifier::Inline : + case ESpecifier::Mutable : + case ESpecifier::NeverInline : + case ESpecifier::Static : + case ESpecifier::Volatile : + break; + + case ESpecifier::Consteval : + expects_function = true; + break; + + case ESpecifier::Const : + ignore_spec = true; + break; + + default : + StrC spec_str = ESpecifier::to_str( spec ); + + log_failure( "Invalid specifier %.*s for variable\n%s", spec_str.Len, spec_str, Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + if ( ignore_spec ) + break; + + specs_found[NumSpecifiers] = spec; + NumSpecifiers++; + eat( currtok.Type ); + } + + if ( NumSpecifiers ) + { + specifiers = def_specifiers( NumSpecifiers, specs_found ); + } + // + } + //! Fallthrough intentional + case TokType::Identifier : + case TokType::Spec_Const : + case TokType::Type_Long : + case TokType::Type_Short : + case TokType::Type_Signed : + case TokType::Type_Unsigned : + case TokType::Type_bool : + case TokType::Type_char : + case TokType::Type_double : + case TokType::Type_int : + { + Code constructor_destructor = parse_global_nspace_constructor_destructor( specifiers ); + // Possible constructor implemented at global file scope. + if ( constructor_destructor ) + { + member = constructor_destructor; + break; + } + + bool found_operator_cast_outside_class_implmentation = false; + s32 idx = Context.Tokens.Idx; + + for ( ; idx < Context.Tokens.Arr.num(); idx++ ) + { + Token tok = Context.Tokens[idx]; + + if ( tok.Type == TokType::Identifier ) + { + idx++; + tok = Context.Tokens[idx]; + if ( tok.Type == TokType::Access_StaticSymbol ) + continue; + + break; + } + + if ( tok.Type == TokType::Decl_Operator ) + found_operator_cast_outside_class_implmentation = true; + + break; + } + + if ( found_operator_cast_outside_class_implmentation ) + { + member = parse_operator_cast(); + // ::operator () { ... } + break; + } + + member = parse_operator_function_or_variable( expects_function, attributes, specifiers ); + // ... + } + } + + if ( member == Code::Invalid ) + { + log_failure( "Failed to parse member\n%s", Context.to_string() ); + Context.pop(); + return CodeInvalid; + } + + // log_fmt("Global Body Member: %s", member->debug_str()); + result.append( member ); + } + + if ( which != Global_Body ) + eat( TokType::BraceCurly_Close ); + // { } + + Context.pop(); + return result; + } + + internal inline Code parse_global_nspace_constructor_destructor( CodeSpecifiers specifiers ) + { + Code result = { nullptr }; + + /* + To check if a definition is for a constructor we can go straight to the opening parenthesis for its parameters + From There we work backwards to see if we come across two identifiers with the same name between an member access + :: operator, there can be template parameters on the left of the :: so we ignore those. + Whats important is that its back to back. + + This has multiple possible faults. What we parse using this method may not filter out if something has a "return type" + This is bad since technically you could have a namespace nested into another namespace with the same name. + If this awful pattern is done the only way to distiguish with this coarse parse is to know there is no return type defined. + + TODO(Ed): We could fix this by attempting to parse a type, but we would have to have a way to have it soft fail and rollback. + */ + TokArray tokens = Context.Tokens; + + s32 idx = tokens.Idx; + Token nav = tokens[idx]; + for ( ; idx < tokens.Arr.num(); idx++, nav = tokens[idx] ) + { + if ( nav.Text[0] == '<' ) + { + // Skip templated expressions as they mey have expressions with the () operators + s32 capture_level = 0; + s32 template_level = 0; + for ( ; idx < tokens.Arr.num(); idx++, nav = tokens[idx] ) + { + if ( nav.Text[0] == '<' ) + ++template_level; + + if ( nav.Text[0] == '>' ) + --template_level; + if ( nav.Type == TokType::Operator && nav.Text[1] == '>' ) + --template_level; + + if ( nav.Type == ETokType::Capture_Start ) + { + if ( template_level != 0 ) + ++capture_level; + else + break; + } + + if ( template_level != 0 && nav.Type == ETokType::Capture_End ) + --capture_level; + } + } + + if ( nav.Type == TokType::Capture_Start ) + break; + } + + --idx; + Token tok_right = tokens[idx]; + Token tok_left = NullToken; + + if ( tok_right.Type != TokType::Identifier ) + { + // We're not dealing with a constructor if there is no identifier right before the opening of a parameter's scope. + return result; + } + + --idx; + tok_left = tokens[idx]; + // ... + + bool possible_destructor = false; + if ( tok_left.Type == TokType::Operator && tok_left.Text[0] == '~' ) + { + possible_destructor = true; + --idx; + tok_left = tokens[idx]; + } + + if ( tok_left.Type != TokType::Access_StaticSymbol ) + return result; + + --idx; + tok_left = tokens[idx]; + // ... :: + + // We search toward the left until we find the next valid identifier + s32 capture_level = 0; + s32 template_level = 0; + while ( idx != tokens.Idx ) + { + if ( tok_left.Text[0] == '<' ) + ++template_level; + + if ( tok_left.Text[0] == '>' ) + --template_level; + if ( tok_left.Type == TokType::Operator && tok_left.Text[1] == '>' ) + --template_level; + + if ( template_level != 0 && tok_left.Type == ETokType::Capture_Start ) + ++capture_level; + + if ( template_level != 0 && tok_left.Type == ETokType::Capture_End ) + --capture_level; + + if ( capture_level == 0 && template_level == 0 && tok_left.Type == TokType::Identifier ) + break; + + --idx; + tok_left = tokens[idx]; + } + + bool is_same = str_compare( tok_right.Text, tok_left.Text, tok_right.Length ) == 0; + if ( tok_left.Type == TokType::Identifier && is_same ) + { + // We have found the pattern we desired + if ( possible_destructor ) + { + // :: ~ ( + result = parse_destructor( specifiers ); + } + else + { + // :: ( + result = parse_constructor( specifiers ); + } + } + + return result; + } + + // TODO(Ed): I want to eventually change the identifier to its own AST type. + // This would allow distinction of the qualifier for a symbol :: + // This would also allow + internal Token parse_identifier( bool* possible_member_function ) + { + push_scope(); + + Token name = currtok; + Context.Scope->Name = name; + eat( TokType::Identifier ); + // + + parse_template_args( name ); + //