230 lines
6.9 KiB
C#
230 lines
6.9 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using EpicGames.Core;
|
|
using EpicGames.UHT.Utils;
|
|
|
|
namespace EpicGames.UHT.Tokenizer
|
|
{
|
|
/// <summary>
|
|
/// Collection of assorted utility token reader extensions
|
|
/// </summary>
|
|
public static class UhtTokenReaderUtilityExtensions
|
|
{
|
|
private static readonly HashSet<StringView> s_skipDeclarationWarningStrings = new()
|
|
{
|
|
"GENERATED_BODY",
|
|
"GENERATED_IINTERFACE_BODY",
|
|
"GENERATED_UCLASS_BODY",
|
|
"GENERATED_UINTERFACE_BODY",
|
|
"GENERATED_USTRUCT_BODY",
|
|
// Leaving these disabled ATM since they can exist in the code without causing compile issues
|
|
//"RIGVM_METHOD",
|
|
//"UCLASS",
|
|
//"UDELEGATE",
|
|
//"UENUM",
|
|
//"UFUNCTION",
|
|
//"UINTERFACE",
|
|
//"UPROPERTY",
|
|
//"USTRUCT",
|
|
};
|
|
|
|
/// <summary>
|
|
/// Try to parse an optional _API macro
|
|
/// </summary>
|
|
/// <param name="tokenReader">Token reader</param>
|
|
/// <param name="apiMacroToken">_API macro parsed</param>
|
|
/// <returns>True if an _API macro was parsed</returns>
|
|
public static bool TryOptionalAPIMacro(this IUhtTokenReader tokenReader, out UhtToken apiMacroToken)
|
|
{
|
|
ref UhtToken token = ref tokenReader.PeekToken();
|
|
if (token.IsIdentifier() && token.Value.Span.EndsWith("_API"))
|
|
{
|
|
apiMacroToken = token;
|
|
tokenReader.ConsumeToken();
|
|
return true;
|
|
}
|
|
apiMacroToken = new UhtToken();
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a declaration/statement that starts with the given token, skip the declaration in the header.
|
|
/// </summary>
|
|
/// <param name="tokenReader">Token reader</param>
|
|
/// <param name="token">Token that started the process</param>
|
|
/// <returns>true if there could be more header to process, false if the end was reached.</returns>
|
|
public static bool SkipDeclaration(this IUhtTokenReader tokenReader, ref UhtToken token)
|
|
{
|
|
// Consume all tokens until the end of declaration/definition has been found.
|
|
int nestedScopes = 0;
|
|
bool endOfDeclarationFound = false;
|
|
|
|
// Store the current value of PrevComment so it can be restored after we parsed everything.
|
|
{
|
|
using UhtTokenDisableComments disableComments = new(tokenReader);
|
|
|
|
// Check if this is a class/struct declaration in which case it can be followed by member variable declaration.
|
|
bool possiblyClassDeclaration = token.IsIdentifier() && (token.IsValue("class") || token.IsValue("struct"));
|
|
|
|
// (known) macros can end without ; or } so use () to find the end of the declaration.
|
|
// However, we don't want to use it with DECLARE_FUNCTION, because we need it to be treated like a function.
|
|
bool macroDeclaration = IsProbablyAMacro(token.Value) && !token.IsIdentifier("DECLARE_FUNCTION");
|
|
|
|
bool definitionFound = false;
|
|
char openingBracket = macroDeclaration ? '(' : '{';
|
|
char closingBracket = macroDeclaration ? ')' : '}';
|
|
|
|
bool retestCurrentToken = false;
|
|
while (true)
|
|
{
|
|
if (!retestCurrentToken)
|
|
{
|
|
token = tokenReader.GetToken();
|
|
if (token.TokenType.IsEndType())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retestCurrentToken = false;
|
|
}
|
|
ReadOnlySpan<char> span = token.Value.Span;
|
|
|
|
// If this is a macro, consume it
|
|
// If we find parentheses at top-level and we think it's a class declaration then it's more likely
|
|
// to be something like: class UThing* GetThing();
|
|
if (possiblyClassDeclaration && nestedScopes == 0 && token.IsSymbol() && span.Length == 1 && span[0] == '(')
|
|
{
|
|
possiblyClassDeclaration = false;
|
|
}
|
|
|
|
if (token.IsSymbol() && span.Length == 1 && span[0] == ';' && nestedScopes == 0)
|
|
{
|
|
endOfDeclarationFound = true;
|
|
break;
|
|
}
|
|
|
|
if (token.IsIdentifier())
|
|
{
|
|
// Use a trivial pre-filter to avoid doing the search on things that aren't UE keywords we care about
|
|
if (span[0] == 'G' || span[0] == 'R' || span[0] == 'U')
|
|
{
|
|
if (s_skipDeclarationWarningStrings.Contains(token.Value))
|
|
{
|
|
tokenReader.LogWarning($"The identifier \'{token.Value}\' was detected in a block being skipped. Was this intentional?");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!macroDeclaration && token.IsIdentifier() && span.Equals("PURE_VIRTUAL", StringComparison.Ordinal) && nestedScopes == 0)
|
|
{
|
|
openingBracket = '(';
|
|
closingBracket = ')';
|
|
}
|
|
|
|
if (token.IsSymbol() && span.Length == 1 && span[0] == openingBracket)
|
|
{
|
|
// This is a function definition or class declaration.
|
|
definitionFound = true;
|
|
nestedScopes++;
|
|
}
|
|
else if (token.IsSymbol() && span.Length == 1 && span[0] == closingBracket)
|
|
{
|
|
nestedScopes--;
|
|
if (nestedScopes == 0)
|
|
{
|
|
// Could be a class declaration in all capitals, and not a macro
|
|
bool reallyEndDeclaration = true;
|
|
if (macroDeclaration)
|
|
{
|
|
reallyEndDeclaration = !tokenReader.TryPeekOptional('{');
|
|
}
|
|
|
|
if (reallyEndDeclaration)
|
|
{
|
|
endOfDeclarationFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nestedScopes < 0)
|
|
{
|
|
throw new UhtException(tokenReader, token.InputLine, $"Unexpected '{closingBracket}'. Did you miss a semi-colon?");
|
|
}
|
|
}
|
|
else if (macroDeclaration && nestedScopes == 0)
|
|
{
|
|
macroDeclaration = false;
|
|
openingBracket = '{';
|
|
closingBracket = '}';
|
|
retestCurrentToken = true;
|
|
}
|
|
}
|
|
if (endOfDeclarationFound)
|
|
{
|
|
// Member variable declaration after class declaration (see bPossiblyClassDeclaration).
|
|
if (possiblyClassDeclaration && definitionFound)
|
|
{
|
|
// Should syntax errors be also handled when someone declares a variable after function definition?
|
|
// Consume the variable name.
|
|
if (tokenReader.IsEOF)
|
|
{
|
|
return true;
|
|
}
|
|
if (tokenReader.TryOptionalIdentifier())
|
|
{
|
|
tokenReader.Require(';');
|
|
}
|
|
}
|
|
|
|
// C++ allows any number of ';' after member declaration/definition.
|
|
while (tokenReader.TryOptional(';'))
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
// Successfully consumed C++ declaration unless mismatched pair of brackets has been found.
|
|
return nestedScopes == 0 && endOfDeclarationFound;
|
|
}
|
|
|
|
private static bool IsProbablyAMacro(StringView identifier)
|
|
{
|
|
ReadOnlySpan<char> span = identifier.Span;
|
|
if (span.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Macros must start with a capitalized alphanumeric character or underscore
|
|
char firstChar = span[0];
|
|
if (firstChar != '_' && (firstChar < 'A' || firstChar > 'Z'))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Test for known delegate and event macros.
|
|
if (span.StartsWith("DECLARE_MULTICAST_DELEGATE") ||
|
|
span.StartsWith("DECLARE_DELEGATE") ||
|
|
span.StartsWith("DECLARE_EVENT"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Failing that, we'll guess about it being a macro based on it being a fully-capitalized identifier.
|
|
foreach (char ch in span[1..])
|
|
{
|
|
if (ch != '_' && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9'))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|