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