// 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