Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.UHT/Parsers/UhtEnumParser.cs
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

272 lines
9.3 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System.Diagnostics.CodeAnalysis;
using EpicGames.Core;
using EpicGames.UHT.Tables;
using EpicGames.UHT.Tokenizer;
using EpicGames.UHT.Types;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Parsers
{
/// <summary>
/// UENUM parser
/// </summary>
[UnrealHeaderTool]
public static class UhtEnumParser
{
#region Keywords
[UhtKeyword(Extends = UhtTableNames.Global)]
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Attribute accessed method")]
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Attribute accessed method")]
private static UhtParseResult UENUMKeyword(UhtParsingScope topScope, UhtParsingScope actionScope, ref UhtToken token)
{
return ParseUEnum(topScope, token);
}
#endregion
private static UhtParseResult ParseUEnum(UhtParsingScope parentScope, UhtToken keywordToken)
{
UhtEnum enumObject = new(parentScope.HeaderFile, parentScope.HeaderParser.GetNamespace(), parentScope.ScopeType, keywordToken.InputLine);
{
using UhtParsingScope topScope = new(parentScope, enumObject, parentScope.Session.GetKeywordTable(UhtTableNames.Enum), UhtAccessSpecifier.Public);
const string ScopeName = "UENUM";
{
using UhtMessageContext tokenContext = new(ScopeName);
// Parse the specifiers
UhtSpecifierContext specifierContext = new(topScope, topScope.TokenReader, enumObject.MetaData);
UhtSpecifierParser specifiers = UhtSpecifierParser.GetThreadInstance(specifierContext, ScopeName, parentScope.Session.GetSpecifierTable(UhtTableNames.Enum));
specifiers.ParseSpecifiers();
// Read the name and the CPP type
if (topScope.TokenReader.TryOptional("namespace"))
{
enumObject.CppForm = UhtEnumCppForm.Namespaced;
}
else
{
topScope.TokenReader.OptionalAttributes(false);
if (topScope.TokenReader.TryOptional("enum"))
{
enumObject.CppForm = topScope.TokenReader.TryOptional("class") || topScope.TokenReader.TryOptional("struct") ? UhtEnumCppForm.EnumClass : UhtEnumCppForm.Regular;
}
else
{
throw new UhtTokenException(topScope.TokenReader, topScope.TokenReader.PeekToken(), null);
}
}
topScope.TokenReader.OptionalAttributes(false);
UhtToken enumToken = topScope.TokenReader.GetIdentifier("enumeration name");
enumObject.SourceName = enumToken.Value.ToString();
specifiers.ParseFieldMetaData();
specifiers.ParseDeferred();
enumObject.Outer?.AddChild(enumObject);
UhtCompilerDirective compilerDirective = topScope.HeaderParser.GetCurrentCompositeCompilerDirective();
if (compilerDirective.HasAnyFlags(UhtCompilerDirective.WithEditorOnlyData))
{
enumObject.DefineScope |= UhtDefineScope.EditorOnlyData;
}
enumObject.DefineScope |= compilerDirective.GetDefaultDefineScopes();
// Read base for enum class
if (enumObject.CppForm == UhtEnumCppForm.EnumClass)
{
ParseUnderlyingType(topScope, enumObject);
if (enumObject.UnderlyingType != UhtEnumUnderlyingType.Uint8 && enumObject.MetaData.ContainsKey("BlueprintType"))
{
topScope.TokenReader.LogError("Invalid BlueprintType enum base - currently only uint8 supported");
}
}
else
{
if (enumObject.CppForm == UhtEnumCppForm.Regular)
{
ParseUnderlyingType(topScope, enumObject);
}
if ((enumObject.EnumFlags & EEnumFlags.Flags) != 0)
{
topScope.TokenReader.LogError("The 'Flags' specifier can only be used on enum classes");
}
}
//EnumDef.GetDefinitionRange().Start = &Input[InputPos];
// Get the opening brace
topScope.TokenReader.Require('{');
switch (enumObject.CppForm)
{
case UhtEnumCppForm.Namespaced:
// Now handle the inner true enum portion
topScope.TokenReader.OptionalAttributes(false);
topScope.TokenReader.Require("enum");
topScope.TokenReader.OptionalAttributes(true);
UhtToken innerEnumToken = topScope.TokenReader.GetIdentifier("enumeration type name");
ParseUnderlyingType(topScope, enumObject);
topScope.TokenReader.Require('{');
enumObject.CppType = $"{enumObject.SourceName}::{innerEnumToken.Value}";
break;
case UhtEnumCppForm.EnumClass:
case UhtEnumCppForm.Regular:
enumObject.CppType = enumObject.SourceName;
break;
}
if (enumObject.CppForm != UhtEnumCppForm.EnumClass && enumObject.UnderlyingType == UhtEnumUnderlyingType.Unspecified)
{
string logMessage = $"Underlying type must be specified.";
switch (topScope.Session.Config!.EnumUnderlyingTypeNotSet.GetBehavior(enumObject.Module))
{
case UhtIssueBehavior.AllowSilently:
break;
case UhtIssueBehavior.AllowAndLog:
topScope.TokenReader.LogTrace(enumToken.InputLine, logMessage);
break;
default:
topScope.TokenReader.LogError(enumToken.InputLine, logMessage);
break;
}
}
tokenContext.Reset($"UENUM {enumObject.SourceName}");
topScope.AddModuleRelativePathToMetaData();
topScope.AddFormattedCommentsAsTooltipMetaData();
// Parse all enum tags
bool hasUnparsedValue = false;
long currentEnumValue = 0;
topScope.TokenReader.RequireList('}', ',', true, () =>
{
UhtToken tagToken = topScope.TokenReader.GetIdentifier();
if (tagToken.IsValue("true", true) || tagToken.IsValue("false", true))
{
// C++ UHT compatibility - TODO
topScope.TokenReader.LogError("Enumerations can't have any elements named 'true' or 'false' regardless of case");
}
StringView fullEnumName;
switch (enumObject.CppForm)
{
case UhtEnumCppForm.Namespaced:
case UhtEnumCppForm.EnumClass:
fullEnumName = new StringView($"{enumObject.SourceName}::{tagToken.Value}");
break;
case UhtEnumCppForm.Regular:
fullEnumName = tagToken.Value;
break;
default:
throw new UhtIceException("Unexpected EEnumCppForm value");
}
// Save the new tag with a default value. This will be replaced later
//COMPATIBILITY-TODO: If a enum value has a comment and a tooltip, it will generate an error.
// This is the reverse of all other parsing. This code should be modified to process the comment
// after the UMETA.
int enumIndex = enumObject.AddEnumValue(tagToken.Value.ToString(), 0);
topScope.AddFormattedCommentsAsTooltipMetaData(enumIndex);
// Skip any deprecation
topScope.TokenReader.OptionalAttributes(false);
// Try to read an optional explicit enum value specification
if (topScope.TokenReader.TryOptional('='))
{
bool parsedValue = topScope.TokenReader.TryOptionalConstLong(out long scatchValue);
if (parsedValue)
{
currentEnumValue = scatchValue;
}
else
{
// We didn't parse a literal, so set an invalid value
currentEnumValue = -1;
hasUnparsedValue = true;
}
// Skip tokens until we encounter a comma, a closing brace or a UMETA declaration
// There are tokens after the initializer so it's not a standalone literal,
// so set it to an invalid value.
int skippedCount = topScope.TokenReader.ConsumeUntil(new string[] { ",", "}", "UMETA" });
if (skippedCount == 0 && !parsedValue)
{
throw new UhtTokenException(topScope.TokenReader, topScope.TokenReader.PeekToken(), "enumerator initializer");
}
if (skippedCount > 0)
{
currentEnumValue = -1;
hasUnparsedValue = true;
}
}
// Save the value and auto increment
UhtEnumValue value = enumObject.EnumValues[enumIndex];
value.Value = currentEnumValue;
enumObject.EnumValues[enumIndex] = value;
if (currentEnumValue != -1)
{
++currentEnumValue;
}
enumObject.MetaData.Add(UhtNames.Name, enumIndex, enumObject.EnumValues[enumIndex].Name.ToString());
// check for metadata on this enum value
specifierContext.MetaNameIndex = enumIndex;
specifiers.ParseFieldMetaData();
});
// Trailing brace and semicolon for the enum
topScope.TokenReader.Require(';');
if (enumObject.CppForm == UhtEnumCppForm.Namespaced)
{
topScope.TokenReader.Require('}');
}
if (!hasUnparsedValue && !enumObject.IsValidEnumValue(0) && enumObject.MetaData.ContainsKey(UhtNames.BlueprintType))
{
enumObject.LogWarning($"'{enumObject.SourceName}' does not have a 0 entry! (This is a problem when the enum is initialized by default)");
}
}
return UhtParseResult.Handled;
}
}
private static void ParseUnderlyingType(UhtParsingScope topScope, UhtEnum enumObject)
{
if (topScope.TokenReader.TryOptional(':'))
{
UhtToken enumType = topScope.TokenReader.GetIdentifier("enumeration base");
if (!System.Enum.TryParse<UhtEnumUnderlyingType>(enumType.Value.ToString(), true, out UhtEnumUnderlyingType underlyingType) || underlyingType == UhtEnumUnderlyingType.Unspecified)
{
topScope.TokenReader.LogError(enumType.InputLine, $"Unsupported enum underlying base type '{enumType.Value}'");
}
enumObject.UnderlyingType = underlyingType;
}
else
{
enumObject.UnderlyingType = UhtEnumUnderlyingType.Unspecified;
}
}
}
}