// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using EpicGames.Core; using EpicGames.UHT.Tables; using UnrealBuildBase; namespace EpicGames.UHT.Utils { /// /// Defines all the table names for the standard UHT types /// public static class UhtTableNames { /// /// The class base table is common to UCLASS, UINTERFACE and native interfaces /// public const string ClassBase = "ClassBase"; /// /// Table for UCLASS /// public const string Class = "Class"; /// /// Default table applied to all other tables /// public const string Default = "Default"; /// /// Table for UENUM /// public const string Enum = "Enum"; /// /// Table for all types considered a UField /// public const string Field = "Field"; /// /// Table for all functions /// public const string Function = "Function"; /// /// Table for the global/file scope /// public const string Global = "Global"; /// /// Table for UINTERFACE /// public const string Interface = "Interface"; /// /// Table for interfaces /// public const string NativeInterface = "NativeInterface"; /// /// Table for all UObject types /// public const string Object = "Object"; /// /// Table for properties that are function arguments /// public const string PropertyArgument = "PropertyArgument"; /// /// Table for properties that are struct/class members /// public const string PropertyMember = "PropertyMember"; /// /// Table for USTRUCT /// public const string ScriptStruct = "ScriptStruct"; /// /// Table for all UStruct objects (structs, classes, and functions) /// public const string Struct = "Struct"; } /// /// Base class for table lookup system. /// public class UhtLookupTableBase { /// /// This table inherits entries for the given table /// public UhtLookupTableBase? ParentTable { get; set; } = null; /// /// Name of the table /// public string TableName { get; set; } = String.Empty; /// /// User facing name of the table /// public string UserName { get => String.IsNullOrEmpty(_userName) ? TableName : _userName; set => _userName = value; } /// /// Check to see if the table is internal /// public bool Internal { get; set; } = false; /// /// If true, this table has been implemented and not just created on demand by another table /// public bool Implemented { get; set; } = false; /// /// Internal version of the user name. If it hasn't been set, then the table name will be used /// private string _userName = String.Empty; /// /// Merge the lookup table. Duplicates will be ignored. /// /// Base table being merged public virtual void Merge(UhtLookupTableBase baseTable) { } /// /// Given a method name, try to extract the entry name for a table /// /// Class containing the method /// Method information /// Optional name supplied by the attributes. If specified, this name will be returned instead of extracted from the method name /// Required suffix /// The extracted name or the supplied name public static string GetSuffixedName(Type classType, MethodInfo methodInfo, string? inName, string suffix) { string name = inName ?? String.Empty; if (String.IsNullOrEmpty(name)) { if (methodInfo.Name.EndsWith(suffix, StringComparison.Ordinal)) { name = methodInfo.Name[..^suffix.Length]; } else { throw new UhtIceException($"The '{suffix}' attribute on the {classType.Name}.{methodInfo.Name} method doesn't have a name specified or the method name doesn't end in '{suffix}'."); } } return name; } } /// /// Lookup tables provide a method of associating actions with given C++ keyword or UE specifier /// /// Keyword or specifier information public class UhtLookupTable : UhtLookupTableBase { /// /// Lookup dictionary for the specifiers /// private readonly Dictionary _lookup; /// /// Construct a new table /// public UhtLookupTable(StringViewComparer comparer) { _lookup = new Dictionary(comparer); } /// /// Add the given value to the lookup table. It will throw an exception if it is a duplicate. /// /// Key to be added /// Value to be added public UhtLookupTable Add(string key, TValue value) { _lookup.Add(key, value); return this; } /// /// Attempt to fetch the value associated with the key /// /// Lookup key /// Value associated with the key /// True if the key was found, false if not public bool TryGetValue(StringView key, [MaybeNullWhen(false)] out TValue value) { return _lookup.TryGetValue(key, out value); } /// /// Merge the lookup table. Duplicates will be ignored. /// /// Base table being merged public override void Merge(UhtLookupTableBase baseTable) { foreach (KeyValuePair kvp in ((UhtLookupTable)baseTable)._lookup) { _lookup.TryAdd(kvp.Key, kvp.Value); } } } /// /// A collection of lookup tables by name. /// /// Table type public class UhtLookupTables where TTable : UhtLookupTableBase, new() { /// /// Collection of named tables /// public Dictionary Tables { get; } = new Dictionary(); /// /// The name of the group of tables /// public string Name { get; set; } /// /// Create a new group of tables /// /// The name of the group public UhtLookupTables(string name) { Name = name; } /// /// Given a table name, return the table. If not found, a new one will be added with the given name. /// /// The name of the table to return /// The table associated with the name. public TTable Get(string tableName) { if (!Tables.TryGetValue(tableName, out TTable? table)) { table = new TTable(); table.TableName = tableName; Tables.Add(tableName, table); } return table; } /// /// Create a table with the given information. If the table already exists, it will be initialized with the given data. /// /// The name of the table /// The user facing name of the name /// The parent table name used to merge table entries /// If true, the table is internal and won't be visible to the user. /// The created table public TTable Create(string tableName, string userName, string? parentTableName, bool tableIsInternal = false) { TTable table = Get(tableName); table.UserName = userName; table.Internal = tableIsInternal; table.Implemented = true; if (!String.IsNullOrEmpty(parentTableName)) { table.ParentTable = Get(parentTableName); } return table; } /// /// Merge the contents of all parent tables into their children. This is done so that the /// parent chain need not be searched when looking for table entries. /// /// Thrown if there are problems with the tables. public void Merge() { List orderedList = new(Tables.Count); List remainingList = new(Tables.Count); HashSet doneTables = new(); // Collect all of the tables foreach (KeyValuePair kvp in Tables) { if (!kvp.Value.Implemented) { throw new UhtIceException($"{Name} table '{kvp.Value.TableName}' has been referenced but not implemented"); } remainingList.Add(kvp.Value); } // Perform a topological sort of the tables while (remainingList.Count != 0) { bool addedOne = false; for (int i = 0; i < remainingList.Count;) { TTable table = remainingList[i]; if (table.ParentTable == null || doneTables.Contains(table.ParentTable)) { orderedList.Add(table); doneTables.Add(table); remainingList[i] = remainingList[^1]; remainingList.RemoveAt(remainingList.Count - 1); addedOne = true; } else { ++i; } } if (!addedOne) { throw new UhtIceException($"Circular dependency in {GetType().Name}.{Name} tables detected"); } } // Merge the tables foreach (TTable table in orderedList) { if (table.ParentTable != null) { table.Merge((TTable)table.ParentTable); } } } } /// /// Bootstraps the standard UHT tables /// public static class UhtStandardTables { /// /// Enumeration that specifies if the table is a specifier and/or keyword table /// [Flags] public enum EUhtCreateTablesFlags { /// /// A keyword table will be created /// Keyword = 1 << 0, /// /// A specifier table will be created /// Specifiers = 1 << 1, } /// /// Create all of the standard scope tables. /// /// UHT tables public static void InitStandardTables(UhtTables tables) { CreateTables(tables, UhtTableNames.Default, "Default", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, null, true); CreateTables(tables, UhtTableNames.Global, "Global", EUhtCreateTablesFlags.Keyword, UhtTableNames.Default, false); CreateTables(tables, UhtTableNames.PropertyArgument, "Argument/Return", EUhtCreateTablesFlags.Specifiers, UhtTableNames.Default, false); CreateTables(tables, UhtTableNames.PropertyMember, "Member", EUhtCreateTablesFlags.Specifiers, UhtTableNames.Default, false); CreateTables(tables, UhtTableNames.Object, "Object", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Default, true); CreateTables(tables, UhtTableNames.Field, "Field", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Object, true); CreateTables(tables, UhtTableNames.Enum, "Enum", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Field, false); CreateTables(tables, UhtTableNames.Struct, "Struct", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Field, true); CreateTables(tables, UhtTableNames.ClassBase, "ClassBase", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Struct, true); CreateTables(tables, UhtTableNames.Class, "Class", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.ClassBase, false); CreateTables(tables, UhtTableNames.Interface, "Interface", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.ClassBase, false); CreateTables(tables, UhtTableNames.NativeInterface, "IInterface", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.ClassBase, false); CreateTables(tables, UhtTableNames.Function, "Function", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Struct, false); CreateTables(tables, UhtTableNames.ScriptStruct, "Struct", EUhtCreateTablesFlags.Specifiers | EUhtCreateTablesFlags.Keyword, UhtTableNames.Struct, false); } /// /// Creates a series of tables given the supplied setup /// /// UHT tables /// Name of the table. /// Name presented to the user via error messages. /// Types of tables to be created. /// Name of the parent table or null for none. /// If true, this table will not be included in any error messages. public static void CreateTables(UhtTables tables, string tableName, string tableUserName, EUhtCreateTablesFlags createTables, string? parentTableName, bool tableIsInternal = false) { if (createTables.HasFlag(EUhtCreateTablesFlags.Keyword)) { tables.KeywordTables.Create(tableName, tableUserName, parentTableName, tableIsInternal); } if (createTables.HasFlag(EUhtCreateTablesFlags.Specifiers)) { tables.SpecifierTables.Create(tableName, tableUserName, parentTableName, tableIsInternal); tables.SpecifierValidatorTables.Create(tableName, tableUserName, parentTableName, tableIsInternal); } } } /// /// This attribute must be applied to any class that contains other UHT attributes. /// [AttributeUsage(AttributeTargets.Class)] public sealed class UnrealHeaderToolAttribute : Attribute { /// /// If specified, this method will be invoked once during the scan for attributes. /// It can be used to perform some one time initialization. /// public string InitMethod { get; set; } = String.Empty; } /// /// UnrealHeaderTool avoids hard coding any table contents by using attributes to add entries to the tables. /// /// There are two basic styles of tables in UHT. /// /// The first style is just a simple association of a string and an attribute. For example, /// the engine class table is just a collection of all the engine class names supported by the engine. /// /// The second style is a table of tables. Depending on the context (i.e. is a "class" or "function" /// being parsed), attributes will "extend" a given table adding an entry to that table and every /// table the derives from that table. For example, the Keywords table will add "private" to the /// "ClassBase" table. Since the "Class", "Interface", and "NativeInterface" tables derive from /// "ClassBase", all of those tables will contain the keyword "private". /// /// See UhtTables.cs for a list of table names and how they relate to each other. /// /// Tables are implemented in the following source files: /// /// UhtEngineClassTable.cs - Collection of all the engine class names. /// UhtKeywordTable.cs - Collection of the C++ keywords that UHT understands. /// UhtLocTextDefaultValueTable.cs - Collection of loctext default value parsing /// UhtPropertyTypeTable.cs - Collection of the property type keywords. /// UhtSpecifierTable.cs - Collection of the known specifiers /// UhtSpecifierValidatorTable.cs - Collection of the specifier validators /// UhtStructDefaultValueTable.cs - Collection of structure default value parsing /// public class UhtTables { /// /// Collection of specifier tables /// public UhtSpecifierTables SpecifierTables { get; } = new UhtSpecifierTables(); /// /// Collection of specifier validator tables /// public UhtSpecifierValidatorTables SpecifierValidatorTables { get; } = new UhtSpecifierValidatorTables(); /// /// Collection of keyword tables /// public UhtKeywordTables KeywordTables { get; } = new UhtKeywordTables(); /// /// Collection of property types /// public UhtPropertyTypeTable PropertyTypeTable { get; } = new UhtPropertyTypeTable(); /// /// Collection of structure default values /// public UhtStructDefaultValueTable StructDefaultValueTable { get; } = new UhtStructDefaultValueTable(); /// /// Collection of engine class types /// public UhtEngineClassTable EngineClassTable { get; } = new UhtEngineClassTable(); /// /// Collection of exporters /// public UhtExporterTable ExporterTable { get; } = new UhtExporterTable(); /// /// Collection loc text default values /// public UhtLocTextDefaultValueTable LocTextDefaultValueTable { get; } = new UhtLocTextDefaultValueTable(); /// /// /// public UhtCodeGeneratorInjectorTable CodeGeneratorInjectorTable { get; } = new UhtCodeGeneratorInjectorTable(); /// /// Construct a new table collection /// public UhtTables() { UhtStandardTables.InitStandardTables(this); CheckForAttributes(Assembly.GetExecutingAssembly()); PerformPostInitialization(); } /// /// Add a collection of plugin assembly file paths /// /// Collection of plugins to load public void AddPlugins(IEnumerable pluginAssembliesFilePaths) { foreach (string assemblyFilePath in pluginAssembliesFilePaths) { CheckForAttributes(LoadAssembly(assemblyFilePath)); } PerformPostInitialization(); } /// /// Check to see if the assembly is a UHT plugin /// /// Path to the assembly file /// public static bool IsUhtPlugin(string assemblyFilePath) { Assembly? assembly = LoadAssembly(assemblyFilePath); if (assembly != null) { foreach (Type type in assembly.SafeGetLoadedTypes()) { if (type.IsClass) { foreach (Attribute classAttribute in type.GetCustomAttributes(false).Cast()) { if (classAttribute is UnrealHeaderToolAttribute || classAttribute is UhtEngineClassAttribute) { return true; } } } } } return false; } /// /// Load the given assembly from the file path /// /// Path to the file /// Assembly if it is already loaded or could be loaded public static Assembly? LoadAssembly(string assemblyFilePath) { Assembly? assembly = FindAssemblyByName(Path.GetFileNameWithoutExtension(assemblyFilePath)); if (assembly == null) { assembly = Assembly.LoadFile(assemblyFilePath); } return assembly; } /// /// Locate the assembly by name /// /// Name of the assembly /// Assembly or null private static Assembly? FindAssemblyByName(string? name) { if (name != null) { foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { AssemblyName assemblyName = assembly.GetName(); if (assemblyName.Name != null && String.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase)) { return assembly; } } } return null; } /// /// Check for UHT attributes on all types in the given assembly /// /// Assembly to scan private void CheckForAttributes(Assembly? assembly) { if (assembly != null) { foreach (Type type in assembly.SafeGetLoadedTypes()) { CheckForAttributes(type); } } } /// /// For the given type, look for any table related attributes /// /// Type in question private void CheckForAttributes(Type type) { if (type.IsClass) { // Loop through the attributes foreach (Attribute classAttribute in type.GetCustomAttributes(false).Cast()) { if (classAttribute is UnrealHeaderToolAttribute parserAttribute) { HandleUnrealHeaderToolAttribute(type, parserAttribute); } else if (classAttribute is UhtEngineClassAttribute engineClassAttribute) { EngineClassTable.OnEngineClassAttribute(engineClassAttribute); } } } } private void PerformPostInitialization() { KeywordTables.Merge(); SpecifierTables.Merge(); SpecifierValidatorTables.Merge(); CodeGeneratorInjectorTable.Sort(); // Invoke this to generate an exception if there is no default _ = PropertyTypeTable.Default; _ = StructDefaultValueTable.Default; } private void HandleUnrealHeaderToolAttribute(Type type, UnrealHeaderToolAttribute parserAttribute) { if (!String.IsNullOrEmpty(parserAttribute.InitMethod)) { MethodInfo? initInfo = type.GetMethod(parserAttribute.InitMethod, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (initInfo == null) { throw new Exception($"Unable to find UnrealHeaderTool attribute InitMethod {parserAttribute.InitMethod}"); } initInfo.Invoke(null, Array.Empty()); } // Scan the methods for things we are interested in foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) { // Scan for attributes we care about foreach (Attribute methodAttribute in methodInfo.GetCustomAttributes()) { if (methodAttribute is UhtSpecifierAttribute specifierAttribute) { SpecifierTables.OnSpecifierAttribute(type, methodInfo, specifierAttribute); } else if (methodAttribute is UhtSpecifierValidatorAttribute specifierValidatorAttribute) { SpecifierValidatorTables.OnSpecifierValidatorAttribute(type, methodInfo, specifierValidatorAttribute); } else if (methodAttribute is UhtKeywordCatchAllAttribute keywordCatchAllAttribute) { KeywordTables.OnKeywordCatchAllAttribute(type, methodInfo, keywordCatchAllAttribute); } else if (methodAttribute is UhtKeywordAttribute keywordAttribute) { KeywordTables.OnKeywordAttribute(type, methodInfo, keywordAttribute); } else if (methodAttribute is UhtPropertyTypeAttribute propertyTypeAttribute) { PropertyTypeTable.OnPropertyTypeAttribute(methodInfo, propertyTypeAttribute); } else if (methodAttribute is UhtStructDefaultValueAttribute structDefaultValueAttribute) { StructDefaultValueTable.OnStructDefaultValueAttribute(methodInfo, structDefaultValueAttribute); } else if (methodAttribute is UhtExporterAttribute exporterAttribute) { ExporterTable.OnExporterAttribute(type, methodInfo, exporterAttribute); } else if (methodAttribute is UhtLocTextDefaultValueAttribute locTextDefaultValueAttribute) { LocTextDefaultValueTable.OnLocTextDefaultValueAttribute(methodInfo, locTextDefaultValueAttribute); } else if (methodAttribute is UhtCodeGeneratorInjectorAttribute codeGeneratorInjectorAttribute) { CodeGeneratorInjectorTable.OnUhtCodeGeneratorInjectorAttribute(type, methodInfo, codeGeneratorInjectorAttribute); } } } } } }