// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using EpicGames.Core;
using EpicGames.UHT.Parsers;
using EpicGames.UHT.Types;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Exporters.CodeGen
{
///
/// Helper class for collection where types exist in different define scope blocks
///
///
public class UhtUsedDefineScopes where T : UhtType
{
private bool _first = true;
private UhtDefineScope _soleScope = UhtDefineScope.Invalid;
private UhtDefineScope _allScopes = UhtDefineScope.None;
private uint _present = 0;
///
/// Collection of instances
///
public List Instances { get; } = new();
///
/// If true, there are no instances
///
public bool IsEmpty => Instances.Count == 0;
///
/// If true, at least one instance had no scope
///
public bool HasNoneScope => HasScope(UhtDefineScope.None);
///
/// If all types share the same scope, then the sole scope is that scope. The value will be Invalid if the
/// instances have different scopes.
///
public UhtDefineScope SoleScope => _soleScope;
///
/// Collection of all scopes found in the types. If
///
public UhtDefineScope AllScopes => _allScopes;
///
/// If any instance has no scope, then None is returned. Otherwise AllScopes is returned.
///
public UhtDefineScope NoneAwareScopes => HasNoneScope ? UhtDefineScope.None : AllScopes;
///
/// Constructor with no initial instances
///
public UhtUsedDefineScopes()
{
}
///
/// Constructor with initial range of types
///
/// Instances to initially add
public UhtUsedDefineScopes(IEnumerable instances)
{
AddRange(instances);
}
///
/// Add an instance to the collection
///
/// Instance to be added
public void Add(T instance)
{
UhtDefineScope defineScope = instance.DefineScope;
if (_first)
{
_soleScope = defineScope;
_first = false;
}
else if (_soleScope != UhtDefineScope.Invalid && _soleScope != defineScope)
{
_soleScope = UhtDefineScope.Invalid;
}
_allScopes |= defineScope;
_present |= ScopeToMask(defineScope);
Instances.Add(instance);
}
///
/// Add a range of instances to the collection
///
/// Collection of instances
public void AddRange(IEnumerable instances)
{
foreach (T instance in instances)
{
Add(instance);
}
}
///
/// Check to see if the given scope has instances
///
/// Scope to test
/// True if the scope has elements
public bool HasScope(UhtDefineScope defineScope)
{
return (_present & ScopeToMask(defineScope)) != 0;
}
///
/// Update the list to by ordered by the define scope
///
public void OrderByDefineScope()
{
Instances.SortBy(x => x.DefineScope);
}
///
/// Enumerate all of the used defined scopes
///
/// Enumeration
public IEnumerable EnumerateDefinedScopes()
{
ulong present = _present;
for (int index = 0; present != 0; ++index, present >>= 1)
{
if ((present & 1) != 0)
{
yield return (UhtDefineScope)index;
}
}
}
///
/// Enumerate all of the used defined scopes
///
/// Enumeration
public IEnumerable EnumerateDefinedScopesNoneAtEnd()
{
ulong present = _present >> 1;
for (int index = 1; present != 0; ++index, present >>= 1)
{
if ((present & 1) != 0)
{
yield return (UhtDefineScope)index;
}
}
// At this time, we alway return None
yield return UhtDefineScope.None;
}
private static uint ScopeToMask(UhtDefineScope defineScope)
{
return (uint)1 << (int)defineScope;
}
}
///
/// Helper extensions when working with defined scope collections.
///
/// This class helps with producing consistent and correct code that works with the DefineScope element in UhtType.
///
/// In generated header files, it supports two flavors of macro definitions used to generate the body macro.
///
/// The multi macro style will generate one macro for each unique combination of DefineScope found. Only the instances
/// that match the DefineScope will be placed in that macro. The macros will be generated in such a way that they
/// will always be present and each much be included in the generated body macro. This style is used to generate
/// declarations as needed.
///
/// The single macro style will generate a single macro, but that macro will appear multiple times depending on
/// each combination of the DefineScope combinations required. Each macro will have a complete set of instances
/// filtered by the DefineScope. This style is used to populate such things as enum definitions needed by the engine.
///
/// In generated cpp files, there is support for enumerating through all the instances and emitting the appropriate
/// #if block to include instances based on their DefineScope.
///
internal static class UhtUsedDefineScopesExtensions
{
///
/// Append a macro scoped macro
///
/// Destination builder
/// Which set of scope names will be used
/// Specified scope
/// Action to invoke to append an instance
/// String builder
public static StringBuilder AppendScoped(this StringBuilder builder, UhtDefineScopeNames defineScopeNames, UhtDefineScope defineScope, Action appendAction)
{
using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, defineScope);
appendAction(builder);
return builder;
}
///
/// Append multiple for the given collection of scopes
///
/// Destination builder
/// Defined scope instances
/// Which set of scope names will be used
/// Action to invoke to append an instance
/// String builder
public static StringBuilder AppendMultiScoped(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames,
Action> appendAction) where T : UhtType
{
foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes())
{
AppendScoped(builder, defineScopeNames, defineScope, builder => appendAction(builder, instances.Instances.Where(x => x.DefineScope == defineScope)));
}
return builder;
}
///
/// Append a macro scoped macro
///
/// Destination builder
/// Which set of scope names will be used
/// Specified scope
/// Header code generator
/// Output type owning the instances
/// Macro being created
/// If true, include such things as _EOD onto the macro name
/// Action to invoke to append an instance
/// String builder
public static StringBuilder AppendScopedMacro(this StringBuilder builder, UhtDefineScopeNames defineScopeNames, UhtDefineScope defineScope,
UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, bool includeSuffix, Action appendAction)
{
using (UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, defineScope))
{
using (UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, defineScope, includeSuffix))
{
appendAction(builder);
}
// We can skip writing the macros if there are no properties to declare, as the 'if' and 'else' would be the same
if (defineScope != UhtDefineScope.None)
{
// Trim the extra newlines added after the macro generator
if (builder.Length > 4 &&
builder[^4] == '\r' &&
builder[^3] == '\n' &&
builder[^2] == '\r' &&
builder[^1] == '\n')
{
builder.Length -= 4;
}
builder.AppendElsePreprocessor(defineScope, defineScopeNames);
using UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, defineScope, includeSuffix); // Empty macro
}
}
if (defineScope != UhtDefineScope.None)
{
builder.Append("\r\n\r\n");
}
return builder;
}
///
/// Append multi macros for the given collection of scopes
///
/// Destination builder
/// Defined scope instances
/// Which set of scope names will be used
/// Header code generator
/// Output type owning the instances
/// Macro being created
/// Action to invoke to append an instance
/// String builder
public static StringBuilder AppendMultiScopedMacros(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames,
UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, Action> appendAction) where T : UhtType
{
foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes())
{
AppendScopedMacro(builder, defineScopeNames, defineScope, generator, outerType, macroSuffix, true,
builder => appendAction(builder, instances.Instances.Where(x => x.DefineScope == defineScope)));
}
return builder;
}
///
/// Append the macro definitions requested
///
/// Destination builder
/// Defined scope instances
/// Header code generator
/// Output type owning the instances
/// Macro being created
/// String builder
public static StringBuilder AppendMultiMacroRefs(this StringBuilder builder, UhtUsedDefineScopes instances,
UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix) where T : UhtType
{
foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopes())
{
builder.Append('\t').AppendMacroName(generator, outerType, macroSuffix, defineScope).Append(" \\\r\n");
}
return builder;
}
///
/// Append a single macro, where the macro -same- definition can exist inside of define scopes where
/// the instances contained are all instances that would be satisfied by a scope.
///
/// Destination builder
/// Defined scope instances
/// Which set of scope names will be used
/// Header code generator
/// Output type owning the instances
/// Macro being created
/// Action to invoke to append an instance
/// String builder
public static StringBuilder AppendSingleMacro(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames,
UhtHeaderCodeGenerator generator, UhtType outerType, string macroSuffix, Action> appendAction) where T : UhtType
{
if (instances.SoleScope == UhtDefineScope.None)
{
using UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, UhtDefineScope.None);
appendAction(builder, instances.Instances);
}
else
{
bool first = true;
foreach (UhtDefineScope defineScope in instances.EnumerateDefinedScopesNoneAtEnd())
{
if (first)
{
builder.AppendIfPreprocessor(defineScope, defineScopeNames);
first = false;
}
else if (defineScope != UhtDefineScope.None)
{
builder.AppendElseIfPreprocessor(defineScope, defineScopeNames);
}
else
{
builder.Append("#else\r\n");
}
using (UhtMacroCreator macro = new(builder, generator, outerType, macroSuffix, UhtDefineScope.None))
{
appendAction(builder, instances.Instances.Where(x => (x.DefineScope & ~defineScope) == 0));
}
if (builder.Length > 4 &&
builder[^4] == '\r' &&
builder[^3] == '\n' &&
builder[^2] == '\r' &&
builder[^1] == '\n')
{
builder.Length -= 4;
}
}
builder.Append("#endif\r\n\r\n\r\n");
}
return builder;
}
///
/// Invoke the append action if any types are present. If all types are from the same define scope, then
/// it will be wrapped with and #if block.
///
/// Destination builder
/// Defined scope instances
/// Names to use when outputting the scope
/// Action to invoke
/// String builder
public static StringBuilder AppendIfInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action appendAction) where T : UhtType
{
if (!instances.IsEmpty)
{
using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, instances.NoneAwareScopes);
appendAction(builder);
}
return builder;
}
///
/// Append each instance to the builder
///
/// Destination builder
/// Defined scope instances
/// Names to use when outputting the scope
/// Action to invoke
/// String builder
public static StringBuilder AppendInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, Action appendAction) where T : UhtType
{
return builder.AppendInstances(instances, defineScopeNames, null, appendAction, null);
}
///
/// Append each instance to the builder
///
/// Destination builder
/// Defined scope instances
/// Names to use when outputting the scope
/// Action to invoke prior to first instance
/// Action to invoke
/// Action to invoke following all instances
/// String builder
public static StringBuilder AppendInstances(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames,
Action? preambleAction, Action appendAction, Action? postambleAction) where T : UhtType
{
if (!instances.IsEmpty)
{
using UhtMacroBlockEmitter blockEmitter = new(builder, defineScopeNames, instances.SoleScope);
preambleAction?.Invoke(builder);
foreach (T instance in instances.Instances)
{
blockEmitter.Set(instance.DefineScope);
appendAction(builder, instance);
}
// Make sure the postable run with the proper initial scope
blockEmitter.Set(instances.SoleScope);
postambleAction?.Invoke(builder);
}
return builder;
}
///
/// Append the given array list and count as arguments to a structure constructor
///
/// Destination builder
/// Collected instances
/// Names to use when outputting the scope
/// Name of the statics section
/// The name of the arrray
/// Number of tabs to start the line
/// Text to end the line
/// Destination builder
public static StringBuilder AppendArrayPtrAndCountLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType
{
UhtDefineScope noneAwareScopes = instances.NoneAwareScopes;
// We have no instances in the collection, the list will always be empty
if (instances.IsEmpty)
{
builder
.AppendTabs(tabs)
.Append("nullptr, 0")
.Append(endl);
}
// If we have any instances that have no scope, then we can always just reference the array
else if (noneAwareScopes == UhtDefineScope.None)
{
builder
.AppendTabs(tabs)
.AppendCppName(staticsName, arrayName)
.Append(", UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append(')')
.Append(endl);
}
// If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros
else if (noneAwareScopes == UhtDefineScope.EditorOnlyData)
{
switch (defineScopeNames)
{
case UhtDefineScopeNames.Standard:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITORONLY_DATA(")
.AppendCppName(staticsName, arrayName)
.Append(", nullptr), IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append("), 0)")
.Append(endl);
break;
case UhtDefineScopeNames.WithEditor:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITOR(")
.AppendCppName(staticsName, arrayName)
.Append(", nullptr), IF_WITH_EDITOR(UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append("), 0)")
.Append(endl);
break;
default:
throw new UhtIceException("Unexpected define scope names value");
}
}
// Otherwise we have many different scopes but no none scope, must generate #if block
else
{
builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.AppendCppName(staticsName, arrayName)
.Append(", UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append(')')
.Append(endl);
builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append("nullptr, 0")
.Append(endl);
builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames);
}
return builder;
}
///
/// Append the given array list as arguments to a structure constructor
///
/// Destination builder
/// Collected instances
/// Names to use when outputting the scope
/// Name of the statics section
/// The name of the arrray
/// Number of tabs to start the line
/// Text to end the line
/// Destination builder
public static StringBuilder AppendArrayPtrLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType
{
UhtDefineScope noneAwareScopes = instances.NoneAwareScopes;
// We have no instances in the collection, the list will always be empty
if (instances.IsEmpty)
{
builder
.AppendTabs(tabs)
.Append("nullptr")
.Append(endl);
}
// If we have any instances that have no scope, then we can always just reference the array
else if (noneAwareScopes == UhtDefineScope.None)
{
builder
.AppendTabs(tabs)
.AppendCppName(staticsName, arrayName)
.Append(endl);
}
// If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros
else if (noneAwareScopes == UhtDefineScope.EditorOnlyData)
{
switch (defineScopeNames)
{
case UhtDefineScopeNames.Standard:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITORONLY_DATA(")
.AppendCppName(staticsName, arrayName)
.Append(", nullptr)")
.Append(endl);
break;
case UhtDefineScopeNames.WithEditor:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITOR(")
.AppendCppName(staticsName, arrayName)
.Append(", nullptr)")
.Append(endl);
break;
default:
throw new UhtIceException("Unexpected define scope names value");
}
}
// Otherwise we have many different scopes but no none scope, must generate #if block
else
{
builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.AppendCppName(staticsName, arrayName)
.Append(endl);
builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append("nullptr")
.Append(endl);
builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames);
}
return builder;
}
///
/// Append the given array count as arguments to a structure constructor
///
/// Destination builder
/// Collected instances
/// Names to use when outputting the scope
/// Name of the statics section
/// The name of the arrray
/// Number of tabs to start the line
/// Text to end the line
/// Destination builder
public static StringBuilder AppendArrayCountLine(this StringBuilder builder, UhtUsedDefineScopes instances, UhtDefineScopeNames defineScopeNames, string? staticsName, string arrayName, int tabs, string endl) where T : UhtType
{
UhtDefineScope noneAwareScopes = instances.NoneAwareScopes;
// We have no instances in the collection, the list will always be empty
if (instances.IsEmpty)
{
builder
.AppendTabs(tabs)
.Append('0')
.Append(endl);
}
// If we have any instances that have no scope, then we can always just reference the array
else if (noneAwareScopes == UhtDefineScope.None)
{
builder
.AppendTabs(tabs)
.Append("UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append(')')
.Append(endl);
}
// If the only scope we have is WITH_EDITORONLY_DATA, then we can use the existing macros
else if (noneAwareScopes == UhtDefineScope.EditorOnlyData)
{
switch (defineScopeNames)
{
case UhtDefineScopeNames.Standard:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITORONLY_DATA(UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append("), 0)")
.Append(endl);
break;
case UhtDefineScopeNames.WithEditor:
builder
.AppendTabs(tabs)
.Append("IF_WITH_EDITOR(UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append("), 0)")
.Append(endl);
break;
default:
throw new UhtIceException("Unexpected define scope names value");
}
}
// Otherwise we have many different scopes but no none scope, must generate #if block
else
{
builder.AppendIfPreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append("UE_ARRAY_COUNT(")
.AppendCppName(staticsName, arrayName)
.Append(')')
.Append(endl);
builder.AppendElsePreprocessor(instances.AllScopes, defineScopeNames);
builder
.AppendTabs(tabs)
.Append('0')
.Append(endl);
builder.AppendEndIfPreprocessor(instances.AllScopes, defineScopeNames);
}
return builder;
}
///
/// Start an #if block with the given scope
///
/// String builder
/// Scope
/// Which set of scope names will be used
/// String builder
public static StringBuilder AppendIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard)
{
if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid)
{
builder.Append("#if ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n");
}
return builder;
}
///
/// Start an #else block with the given scope
///
/// String builder
/// Scope
/// Which set of scope names will be used
/// String builder
public static StringBuilder AppendElsePreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard)
{
if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid)
{
builder.Append("#else // ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n");
}
return builder;
}
///
/// Start an #elif block with the given scope
///
/// String builder
/// Scope
/// Which set of scope names will be used
/// String builder
public static StringBuilder AppendElseIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard)
{
if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid)
{
builder.Append("#elif ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n");
}
return builder;
}
///
/// Start an #endif block with the given scope
///
/// String builder
/// Scope
/// Which set of scope names will be used
/// String builder
public static StringBuilder AppendEndIfPreprocessor(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames = UhtDefineScopeNames.Standard)
{
if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid)
{
builder.Append("#endif // ").AppendScopeExpression(defineScope, defineScopeNames).Append("\r\n");
}
return builder;
}
///
/// Append scope expression (i.e. WITH_X && WITH_Y && ...)
///
/// String builder
/// Scope
/// Which set of scope names will be used
/// String builder
public static StringBuilder AppendScopeExpression(this StringBuilder builder, UhtDefineScope defineScope, UhtDefineScopeNames defineScopeNames)
{
if (defineScope != UhtDefineScope.None && defineScope != UhtDefineScope.Invalid)
{
int scopes = (int)defineScope;
bool needAnd = false;
for (int mask = 1; mask <= scopes; mask <<= 1)
{
if ((mask & scopes) != 0)
{
if (needAnd)
{
builder.Append(" && ");
}
builder.Append(((UhtDefineScope)(mask & scopes)).GetCompilerDirective(defineScopeNames).GetCompilerDirectiveText());
needAnd = true;
}
}
}
return builder;
}
private static StringBuilder AppendCppName(this StringBuilder builder, string? staticsName, string arrayName)
{
if (!String.IsNullOrEmpty(staticsName))
{
builder.Append(staticsName).Append("::");
}
return builder.Append(arrayName);
}
}
}