// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Reflection; using EpicGames.Core; using EpicGames.UHT.Parsers; // It would be nice if we didn't need this here using EpicGames.UHT.Tokenizer; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; namespace EpicGames.UHT.Tables { /// /// Defines the different types specifiers relating to their allowed values /// public enum UhtSpecifierValueType { /// /// Internal value used to detect if the attribute has a valid value /// NotSet, /// /// No values of any type are allowed /// None, /// /// A string value but can not be in the form of a list (i.e. =(bob)) /// String, /// /// An optional string value but can not be in the form of a list /// OptionalString, /// /// A string value or a single element string list /// SingleString, /// /// A list of values in key=value pairs /// KeyValuePairList, /// /// A list of values in key=value pairs but the equals is optional /// OptionalEqualsKeyValuePairList, /// /// A list of values. /// StringList, /// /// A list of values and must contain at least one entry /// NonEmptyStringList, /// /// Accepts a string list but the value is ignored by the specifier and is automatically deferred. This is for legacy UHT support. /// Legacy, } /// /// Results from dispatching a specifier /// public enum UhtSpecifierDispatchResults { /// /// Specifier was known and parsed /// Known, /// /// Specified was unknown /// Unknown, } /// /// The specifier context provides the default and simplest information about the specifiers being processed /// public class UhtSpecifierContext { private UhtType? _type = null; private IUhtTokenReader? _tokenReader = null; private IUhtMessageSite? _messageSite = null; private UhtMetaData? _metaData = null; /// /// Get the type containing the specifiers. For properties, this is the outer object and /// not the property itself. /// public UhtType Type { get => _type!; set => _type = value; } /// /// Return the currently active token reader /// public IUhtTokenReader TokenReader { get => _tokenReader!; set => _tokenReader = value; } /// /// Current access specifier /// public UhtAccessSpecifier AccessSpecifier { get; set; } = UhtAccessSpecifier.None; /// /// Message site for messages /// public IUhtMessageSite MessageSite { get => _messageSite!; set => _messageSite = value; } /// /// Meta data currently being parsed. /// public UhtMetaData MetaData { get => _metaData!; set => _metaData = value; } /// /// Make data key index utilized by enumeration values /// public int MetaNameIndex { get; set; } = UhtMetaData.IndexNone; /// /// Construct a new specifier context /// /// /// /// /// public UhtSpecifierContext(UhtParsingScope scope, IUhtMessageSite messageSite, UhtMetaData metaData, int metaNameIndex = UhtMetaData.IndexNone) { Type = scope.ScopeType; TokenReader = scope.TokenReader; AccessSpecifier = scope.AccessSpecifier; MessageSite = messageSite; MetaData = metaData; MetaNameIndex = metaNameIndex; } /// /// Construct an empty context. Scope, MessageSite, and MetaData must be set at a later point /// public UhtSpecifierContext() { } } /// /// Specifiers are either processed immediately when the declaration is parse or deferred until later in the parsing of the object /// public enum UhtSpecifierWhen { /// /// Specifier is parsed when the meta data section is parsed. /// Immediate, /// /// Specifier is executed after more of the object is parsed (but usually before members are parsed) /// Deferred, } /// /// The specifier table contains an instance of UhtSpecifier which is used to dispatch the parsing of /// a specifier to the implementation /// public abstract class UhtSpecifier { /// /// Name of the specifier /// public string Name { get; set; } = String.Empty; /// /// Expected value type /// public UhtSpecifierValueType ValueType { get; set; } /// /// When is the specifier executed /// public UhtSpecifierWhen When { get; set; } = UhtSpecifierWhen.Deferred; /// /// Dispatch an instance of the specifier /// /// Current context /// Specifier value /// Results of the dispatch public abstract UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value); } /// /// Delegate for a specifier with no value /// /// public delegate void UhtSpecifierNoneDelegate(UhtSpecifierContext specifierContext); /// /// Specifier with no value /// public class UhtSpecifierNone : UhtSpecifier { private readonly UhtSpecifierNoneDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierNone(string name, UhtSpecifierWhen when, UhtSpecifierNoneDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.None; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { _delegate(specifierContext); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with a string value /// /// Specifier context /// Specifier value public delegate void UhtSpecifierStringDelegate(UhtSpecifierContext specifierContext, StringView value); /// /// Specifier with a string value /// public class UhtSpecifierString : UhtSpecifier { private readonly UhtSpecifierStringDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierString(string name, UhtSpecifierWhen when, UhtSpecifierStringDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.String; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { if (value == null) { throw new UhtIceException("Required value is null"); } _delegate(specifierContext, (StringView)value); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with an optional string value /// /// Specifier context /// Specifier value public delegate void UhtSpecifierOptionalStringDelegate(UhtSpecifierContext specifierContext, StringView? value); /// /// Specifier with an optional string value /// public class UhtSpecifierOptionalString : UhtSpecifier { private readonly UhtSpecifierOptionalStringDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierOptionalString(string name, UhtSpecifierWhen when, UhtSpecifierOptionalStringDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.OptionalString; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { _delegate(specifierContext, (StringView?)value); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with a string value /// /// Specifier context /// Specifier value public delegate void UhtSpecifierSingleStringDelegate(UhtSpecifierContext specifierContext, StringView value); /// /// Specifier with a string value /// public class UhtSpecifierSingleString : UhtSpecifier { private readonly UhtSpecifierSingleStringDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierSingleString(string name, UhtSpecifierWhen when, UhtSpecifierSingleStringDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.SingleString; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { if (value == null) { throw new UhtIceException("Required value is null"); } _delegate(specifierContext, (StringView)value); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with list of string keys and values /// /// Specifier context /// Specifier value public delegate void UhtSpecifierKeyValuePairListDelegate(UhtSpecifierContext specifierContext, List> value); /// /// Specifier with list of string keys and values /// public class UhtSpecifierKeyValuePairList : UhtSpecifier { private readonly UhtSpecifierKeyValuePairListDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// If true this has an optional KVP list /// Delegate to invoke public UhtSpecifierKeyValuePairList(string name, UhtSpecifierWhen when, bool equalsOptional, UhtSpecifierKeyValuePairListDelegate specifierDelegate) { Name = name; ValueType = equalsOptional ? UhtSpecifierValueType.OptionalEqualsKeyValuePairList : UhtSpecifierValueType.KeyValuePairList; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { if (value == null) { throw new UhtIceException("Required value is null"); } _delegate(specifierContext, (List>)value); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with no value /// /// Specifier context public delegate void UhtSpecifierLegacyDelegate(UhtSpecifierContext specifierContext); /// /// Specifier delegate for legacy UHT specifiers with no value. Will generate a information/deprecation message /// is a value is supplied /// public class UhtSpecifierLegacy : UhtSpecifier { private readonly UhtSpecifierLegacyDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// Delegate to invoke public UhtSpecifierLegacy(string name, UhtSpecifierLegacyDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.StringList; When = UhtSpecifierWhen.Deferred; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { if (value != null) { specifierContext.TokenReader.LogDeprecation($"Specifier '{Name}' has a value which is unused, future versions of UnrealHeaderTool will flag this as an error."); } _delegate(specifierContext); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with an optional string list /// /// Specifier context /// Specifier value public delegate void UhtSpecifierStringListDelegate(UhtSpecifierContext specifierContext, List? value); /// /// Specifier with an optional string list /// public class UhtSpecifierStringList : UhtSpecifier { private readonly UhtSpecifierStringListDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierStringList(string name, UhtSpecifierWhen when, UhtSpecifierStringListDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.StringList; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { _delegate(specifierContext, (List?)value); return UhtSpecifierDispatchResults.Known; } } /// /// Specifier delegate with a list of string views /// /// Specifier context /// Specifier value public delegate void UhtSpecifierNonEmptyStringListDelegate(UhtSpecifierContext specifierContext, List value); /// /// Specifier with a list of string views /// public class UhtSpecifierNonEmptyStringList : UhtSpecifier { private readonly UhtSpecifierNonEmptyStringListDelegate _delegate; /// /// Construct the specifier /// /// Name of the specifier /// When the specifier is executed /// Delegate to invoke public UhtSpecifierNonEmptyStringList(string name, UhtSpecifierWhen when, UhtSpecifierNonEmptyStringListDelegate specifierDelegate) { Name = name; ValueType = UhtSpecifierValueType.NonEmptyStringList; When = when; _delegate = specifierDelegate; } /// public override UhtSpecifierDispatchResults Dispatch(UhtSpecifierContext specifierContext, object? value) { if (value == null) { throw new UhtIceException("Required value is null"); } _delegate(specifierContext, (List)value); return UhtSpecifierDispatchResults.Known; } } /// /// Defines a specifier method /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class UhtSpecifierAttribute : Attribute { /// /// Name of the specifier. If not supplied, the method name must end in "Specifier" and the name will be the method name with "Specifier" stripped. /// public string? Name { get; set; } /// /// Name of the table/scope this specifier applies /// public string? Extends { get; set; } /// /// Value type of the specifier /// public UhtSpecifierValueType ValueType { get; set; } = UhtSpecifierValueType.NotSet; /// /// When the specifier is dispatched /// public UhtSpecifierWhen When { get; set; } = UhtSpecifierWhen.Deferred; } /// /// Collection of specifiers for a given scope /// public class UhtSpecifierTable : UhtLookupTable { /// /// Construct a new specifier table /// public UhtSpecifierTable() : base(StringViewComparer.OrdinalIgnoreCase) { } /// /// Add the given value to the lookup table. It will throw an exception if it is a duplicate. /// /// The specifier to add public UhtSpecifierTable Add(UhtSpecifier specifier) { base.Add(specifier.Name, specifier); return this; } } /// /// Collection of all specifier tables /// public class UhtSpecifierTables : UhtLookupTables { /// /// Construct the specifier table /// public UhtSpecifierTables() : base("specifiers") { } /// /// Invoke for a method that has the specifier attribute /// /// Type containing the method /// Method info /// Specified attributes /// Throw if the attribute isn't properly defined. public void OnSpecifierAttribute(Type type, MethodInfo methodInfo, UhtSpecifierAttribute specifierAttribute) { string name = UhtLookupTableBase.GetSuffixedName(type, methodInfo, specifierAttribute.Name, "Specifier"); if (String.IsNullOrEmpty(specifierAttribute.Extends)) { throw new UhtIceException($"The 'Specifier' attribute on the {type.Name}.{methodInfo.Name} method doesn't have a table specified."); } else { } if (specifierAttribute.ValueType == UhtSpecifierValueType.NotSet) { throw new UhtIceException($"The 'Specifier' attribute on the {type.Name}.{methodInfo.Name} method doesn't have a value type specified."); } UhtSpecifierTable table = Get(specifierAttribute.Extends); switch (specifierAttribute.ValueType) { case UhtSpecifierValueType.None: table.Add(new UhtSpecifierNone(name, specifierAttribute.When, (UhtSpecifierNoneDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierNoneDelegate), methodInfo))); break; case UhtSpecifierValueType.String: table.Add(new UhtSpecifierString(name, specifierAttribute.When, (UhtSpecifierStringDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierStringDelegate), methodInfo))); break; case UhtSpecifierValueType.OptionalString: table.Add(new UhtSpecifierOptionalString(name, specifierAttribute.When, (UhtSpecifierOptionalStringDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierOptionalStringDelegate), methodInfo))); break; case UhtSpecifierValueType.SingleString: table.Add(new UhtSpecifierSingleString(name, specifierAttribute.When, (UhtSpecifierSingleStringDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierSingleStringDelegate), methodInfo))); break; case UhtSpecifierValueType.KeyValuePairList: table.Add(new UhtSpecifierKeyValuePairList(name, specifierAttribute.When, false, (UhtSpecifierKeyValuePairListDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierKeyValuePairListDelegate), methodInfo))); break; case UhtSpecifierValueType.OptionalEqualsKeyValuePairList: table.Add(new UhtSpecifierKeyValuePairList(name, specifierAttribute.When, true, (UhtSpecifierKeyValuePairListDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierKeyValuePairListDelegate), methodInfo))); break; case UhtSpecifierValueType.StringList: table.Add(new UhtSpecifierStringList(name, specifierAttribute.When, (UhtSpecifierStringListDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierStringListDelegate), methodInfo))); break; case UhtSpecifierValueType.NonEmptyStringList: table.Add(new UhtSpecifierNonEmptyStringList(name, specifierAttribute.When, (UhtSpecifierNonEmptyStringListDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierNonEmptyStringListDelegate), methodInfo))); break; case UhtSpecifierValueType.Legacy: table.Add(new UhtSpecifierLegacy(name, (UhtSpecifierLegacyDelegate)Delegate.CreateDelegate(typeof(UhtSpecifierLegacyDelegate), methodInfo))); break; } } } }