// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "uLang/Common/Containers/SharedPointerArray.h" #include "uLang/Common/Misc/Optional.h" #include "uLang/Common/Misc/EnumUtils.h" // for ULANG_ENUM_BIT_FLAGS() #include "uLang/CompilerPasses/IParserPass.h" #include "uLang/CompilerPasses/IPostVstFilter.h" #include "uLang/CompilerPasses/ISemanticAnalyzerPass.h" #include "uLang/CompilerPasses/IPostSemanticAnalysisFilter.h" #include "uLang/CompilerPasses/IPostIrFilter.h" #include "uLang/CompilerPasses/IIRGeneratorPass.h" #include "uLang/CompilerPasses/IAssemblerPass.h" #include "uLang/CompilerPasses/ApiLayerInjections.h" #include "uLang/Semantics/SemanticProgram.h" #include "uLang/Syntax/VstNode.h" // for Vst::Node #include "uLang/SourceProject/SourceProject.h" #include "uLang/SourceProject/VerseVersion.h" #include "uLang/VerseLocalizationGen.h" #define UE_API VERSECOMPILER_API namespace uLang { // Forward declarations class CToolchain; class CUTF8StringView; struct SToolchainParams { /** * --- Parser -------------------------------- * The parser is responsible for ingesting a source file, parsing and tokenizing it, and * generating an abstract syntax tree (Vst) for the rest of the compiler to consume. It * should not need to worry about semantics, just syntax */ TOptional> Parser; /** * --- Post Vst Filters ---------------------- * The post Vst filters take an Vst, and transform it in any way it deems fit. Applications * of this stage can include optimizers (operation reduction, constant folding, etc.), or * metadata stripping or remapping. There can be as many filters as needed, and are called * in array order, starting at 0. It is the loader's responsibility for establishing that * order. */ TSRefArray PostVstFilters; /** * --- Semantic Analyzer ------------------ * The semantic analyzer consumes the generated and optimized VST, transforms it to an AST, * and semantically analyzes the AST, annotating the AST with inferred types and other * analysis products. */ TOptional> SemanticAnalyzer; /** * --- Post Semantic Analysis Filters --------------- * This stage takes the semantically analyzed AST, and performs operations on the result * before passing along to the IR generator. */ TSRefArray PostSemanticAnalysisFilters; /** * --- Post IR generation Filters --------------- * This stage takes the generated IR, and performs operations on the result * before passing along to the assembler. */ TSRefArray PostIrFilters; /** * --- IR (Intermediate Representation) ------- * The IrGenerator creates an intermediate representation intended for lenient analysis and code generation. */ TOptional> IrGenerator; /** * --- Assembler ----------------------------- * The assembler is responsible both for code-gen and linking (resolving any symbols between * language objects). Note that there may be runtime bindings that can't be resolved at this * stage, but all uLang internal bindings should be validated and linked here. */ TOptional> Assembler; /** * --- API Layer Injections --------------- */ SToolchainInjections LayerInjections; }; VERSECOMPILER_API TSRef CreateToolchain(const SToolchainParams& Params); enum class ECompilerResult : uint32_t { Compile_NoOp = (0x00), Compile_RanSyntaxPass = (1<<0), Compile_RanSemanticPass = (1<<1), Compile_RanLocalizationPass = (1<<2), Compile_RanIrPass = (1<<3), Compile_RanCodeGenPass = (1<<4), Compile_SkippedByInjection = (1<<5), Compile_SkippedByEmptyPass = (1<<6), Compile_SyntaxError = (1<<7), Compile_SemanticError = (1<<8), Compile_IrError = (1<<9), Compile_LocalizationError = (1<<10), Compile_CodeGenError = (1<<11), CompileMask_Failure = (Compile_SyntaxError | Compile_SemanticError | Compile_IrError | Compile_LocalizationError | Compile_CodeGenError), CompileMask_Skipped = (Compile_SkippedByInjection | Compile_SkippedByEmptyPass), CompileMask_Aborted = (CompileMask_Failure | CompileMask_Skipped), }; ULANG_ENUM_BIT_FLAGS(ECompilerResult, inline); ULANG_FORCEINLINE bool IsCompileFailure(ECompilerResult E) { return Enum_HasAnyFlags(E, ECompilerResult::CompileMask_Failure); } ULANG_FORCEINLINE bool IsAbortedCompile(ECompilerResult E) { return Enum_HasAnyFlags(E, ECompilerResult::CompileMask_Aborted); } ULANG_FORCEINLINE bool IsCompileIncomplete(ECompilerResult E) { return !E || IsAbortedCompile(E); } ULANG_FORCEINLINE bool IsCompileComplete(ECompilerResult E) { return !IsCompileIncomplete(E); } struct SBuildResults { VERSECOMPILER_API bool HasFailure() const; SBuildResults& operator |= (const SBuildResults& Other); SBuildStatistics _Statistics = {}; bool _IOErrorsFound = false; ECompilerResult _CompilerResult = ECompilerResult::Compile_NoOp; ELinkerResult _LinkerResult = ELinkerResult::Link_Skipped; }; /* Compiler+Linker toolchain **************************************************************************/ /** * The compiler toolchain, which has five stages of compilation. It's structured as a layered, * multi-stage compiler API. Each stage is interchangeable, which means the frontend and the * backend are retargetable. * * This class needs to be assembled by the Toolchain Loader, which uses the Modular Features * to find a module or modules that implement the five stages of the compiler. This also lets * the user mix in various optimizing passes (post-Vst filters), or bytecode packing * (post-expression filters) from any source. */ class CToolchain : public CSharedMix { public: /** * Compile and link all text snippets in the given project */ UE_API SBuildResults BuildProject(const CSourceProject& SourceProject, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Parse a snippet of text and return the resulting Vst snippet * Steps: * 1. Pre parse injections (from both toolchain and build context) * 2. Parse * 3. Post Vst filters * 4. Post parse injections (from both toolchain and build context) */ UE_API ECompilerResult ParseSnippet(const uLang::TSRef& OutVst, const CUTF8StringView& TextSnippet, const SBuildContext& BuildContext, const uint32_t VerseVersion = Verse::Version::Default, const uint32_t UploadedAtFNVersion = VerseFN::UploadedAtFNVersion::Latest); /** * Run SemanticAnalyzeVst and AssembleProgram on an Vst. */ UE_API ECompilerResult CompileVst(const TSRef& Vst, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Run semantic analysis on a given Vst snippet * Steps: * 1. Pre semantic analysis injections (from both toolchain and build context) * 2. Semantic analysis * 3. Post expression filters * 4. Post semantic analysis injections (from both toolchain and build context) */ UE_API ECompilerResult SemanticAnalyzeVst(TOptional>& OutProgram, const TSRef& Vst, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Extract localization information. */ UE_API ECompilerResult ExtractLocalization(const TSRef& Program, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Run IR generation on a given Ast snippet. * Steps: * 1. IR generation * 2. (future lenient analysis) */ UE_API ECompilerResult IrGenerateProgram(const TSRef& Program, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Run semantic analysis and code generation on a given Vst snippet * Steps: * 1. Pre translate injections * 2. Assembler (code generation) */ UE_API ECompilerResult AssembleProgram(const TSRef& Program, const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Run linker (not currently used) */ UE_API ELinkerResult Link(const SBuildContext& BuildContext, const SProgramContext& ProgramContext); /** * Accessors for the various stages of the compiler. Most users will never actually need * to grab these stages individual, but in the cases where you need to do partial compilation, * they can be useful to manually run */ const TOptional>& GetParser() const { return _Params.Parser; } const TSRefArray& GetPostVstFilters() const { return _Params.PostVstFilters; } const TOptional>& GetSemanticAnalyzer() const { return _Params.SemanticAnalyzer; } const TSRefArray& GetPostSemanticAnalysisFilters() const { return _Params.PostSemanticAnalysisFilters; } const TSRefArray& GetPostIrFilters() const { return _Params.PostIrFilters; } const TOptional>& GetAssembler() const { return _Params.Assembler; } const TSPtr& GetProjectVst() const { return _ProjectVst; } /** * Directly sets the cached VST project to the new project specified. * * Warning: doing this will cause the previously cached VST project's destructor to be called, * which means that any AST nodes that still hold references to the VST nodes within will now * have those references be invalidated. The only time you should use this is if you don't care * about the previous VST project anymore and its accompanying AST either. * * @param NewProject The new project to store. */ void SetProjectVst(const TSRef& NewProject) { this->_ProjectVst = NewProject; } // Take localization information, i.e, it removes it from this object. UE_API TArray TakeLocalizationInfo(); // Take string information, i.e, it removes it from this object. UE_API TArray TakeStringInfo(); private: friend VERSECOMPILER_API TSRef CreateToolchain(const SToolchainParams& Params); friend TSRef; UE_API CToolchain(const SToolchainParams& Params); // use CreateToolchain() to construct struct SOrderedPackage { const CSourcePackage* _Package = nullptr; int32_t _DependencyDepth = -1; }; // Build a list of packages ordered by dependency depth // Returns true if successful, or false if glitches were encountered UE_API bool BuildOrderedPackageList(const CSourceProject& SourceProject, const SBuildContext& BuildContext, TArray& OutOrderedPackageList) const; SToolchainParams _Params; TSPtr _ProjectVst; // Localization and string information stored here. TArray _LocalizationInfo; TArray _StringInfo; }; } #undef UE_API