Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.UHT/Tokenizer/UhtTokenReaderFlowControlExtensions.cs
2025-05-18 13:04:45 +08:00

386 lines
14 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Tokenizer
{
/// <summary>
/// Collection of token reader extensions to help with flow control
/// </summary>
public static class UhtTokenReaderFlowControlExtensions
{
/// <summary>
/// Parse an optional list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="action">Action to be invoked for each list element.</param>
/// <returns>True if a list was read</returns>
public static bool TryOptionalList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, Action action)
{
if (tokenReader.TryOptional(initiator))
{
tokenReader.RequireList(terminator, separator, allowTrailingSeparator, action);
return true;
}
return false;
}
/// <summary>
/// Parse a required list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="action">Action to be invoked for each list element.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, Action action)
{
return tokenReader.RequireList(initiator, terminator, separator, allowTrailingSeparator, null, action);
}
/// <summary>
/// Parse a required list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="exceptionContext">Extra context for error messages</param>
/// <param name="action">Action to be invoked for each list element.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, object? exceptionContext, Action action)
{
tokenReader.Require(initiator);
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, exceptionContext, action);
}
/// <summary>
/// Parse a required list. Initiating token must have already been parsed
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="action">Action to be invoked for each list element.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, Action action)
{
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, null, action);
}
/// <summary>
/// Parse a required list. Initiating token must have already been parsed
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="exceptionContext">Extra context for error messages</param>
/// <param name="action">Action to be invoked for each list element.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, object? exceptionContext, Action action)
{
// Check for an empty list
if (tokenReader.TryOptional(terminator))
{
return tokenReader;
}
// Process the body
while (true)
{
// Read the element via the lambda
action();
// Make sure we haven't reached the EOF
if (tokenReader.IsEOF)
{
throw new UhtTokenException(tokenReader, tokenReader.GetToken(), $"'{separator}' or '{terminator}'", exceptionContext);
}
// If we have a separator, then it might be a trailing separator
if (tokenReader.TryOptional(separator))
{
if (allowTrailingSeparator && tokenReader.TryOptional(terminator))
{
return tokenReader;
}
continue;
}
// Otherwise, we must have an terminator
if (!tokenReader.TryOptional(terminator))
{
throw new UhtTokenException(tokenReader, tokenReader.GetToken(), $"'{separator}' or '{terminator}'", exceptionContext);
}
return tokenReader;
}
}
/// <summary>
/// Parse an optional list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="tokensDelegate">Delegate to invoke with the parsed token list</param>
/// <returns>True if a list was read</returns>
public static bool TryOptionalList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, UhtTokensDelegate tokensDelegate)
{
if (tokenReader.TryOptional(initiator))
{
tokenReader.RequireList(terminator, separator, allowTrailingSeparator, tokensDelegate);
return true;
}
return false;
}
/// <summary>
/// Parse a required list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="tokensDelegate">Delegate to invoke with the parsed token list</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, UhtTokensDelegate tokensDelegate)
{
return tokenReader.RequireList(initiator, terminator, separator, allowTrailingSeparator, null, tokensDelegate);
}
/// <summary>
/// Parse a required list
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">Initiating symbol</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="exceptionContext">Extra context for error messages</param>
/// <param name="tokensDelegate">Delegate to invoke with the parsed token list</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char initiator, char terminator, char separator, bool allowTrailingSeparator, object? exceptionContext, UhtTokensDelegate tokensDelegate)
{
tokenReader.Require(initiator);
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, exceptionContext, tokensDelegate);
}
/// <summary>
/// Parse a required list. Initiating token must have already been parsed
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="tokensDelegate">Delegate to invoke with the parsed token list</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, UhtTokensDelegate tokensDelegate)
{
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, null, tokensDelegate);
}
/// <summary>
/// Parse a required list. Initiating token must have already been parsed
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminator">Terminating symbol</param>
/// <param name="separator">Separator symbol</param>
/// <param name="allowTrailingSeparator">If true, allow trailing separators</param>
/// <param name="exceptionContext">Extra context for error messages</param>
/// <param name="tokensDelegate">Delegate to invoke with the parsed token list</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, object? exceptionContext, UhtTokensDelegate tokensDelegate)
{
// Check for an empty list
if (tokenReader.TryOptional(terminator))
{
return tokenReader;
}
// Process the body
while (true)
{
List<UhtToken> tokens = new();
// Read the tokens until we hit the end
while (true)
{
// Make sure we haven't reached the EOF
if (tokenReader.IsEOF)
{
throw new UhtTokenException(tokenReader, tokenReader.GetToken(), $"'{separator}' or '{terminator}'", exceptionContext);
}
// If we have a separator, then it might be a trailing separator
if (tokenReader.TryOptional(separator))
{
tokensDelegate(tokens);
if (tokenReader.TryOptional(terminator))
{
if (allowTrailingSeparator)
{
return tokenReader;
}
throw new UhtException(tokenReader, $"A separator '{separator}' followed immediately by the terminator '{terminator}' is invalid");
}
break;
}
// If this is the terminator, then we are done
if (tokenReader.TryOptional(terminator))
{
tokensDelegate(tokens);
return tokenReader;
}
tokens.Add(tokenReader.GetToken());
}
}
}
/// <summary>
/// Consume a block of tokens bounded by the two given symbols.
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="initiator">The next token must be the given symbol.</param>
/// <param name="terminator">The tokens are read until the given symbol is found. The terminating symbol will be consumed.</param>
/// <param name="exceptionContext">Extra context for any error messages</param>
/// <returns>The input token reader</returns>
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char initiator, char terminator, object? exceptionContext = null)
{
tokenReader.Require(initiator);
// Process the body
while (true)
{
// Make sure we haven't reached the EOF
if (tokenReader.IsEOF)
{
throw new UhtTokenException(tokenReader, tokenReader.GetToken(), $"'{terminator}'", exceptionContext);
}
// Look for the terminator
if (tokenReader.TryOptional(terminator))
{
break;
}
// Consume the current token
tokenReader.ConsumeToken();
}
return tokenReader;
}
/// <summary>
/// Invoke action while the next token is the given string
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="text">Text to match to continue invoking Action</param>
/// <param name="action">Action to invoke if and only if the prior text was parsed.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader While(this IUhtTokenReader tokenReader, string text, Action action)
{
while (tokenReader.TryOptional(text))
{
action();
}
return tokenReader;
}
/// <summary>
/// Invoke action while the next token is the given string
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="text">Text to match to continue invoking Action</param>
/// <param name="action">Action to invoke if and only if the prior text was parsed.</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader While(this IUhtTokenReader tokenReader, char text, Action action)
{
while (tokenReader.TryOptional(text))
{
action();
}
return tokenReader;
}
/// <summary>
/// Consume tokens until one of the strings are found. Terminating token will not be consumed.
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminators">Strings that will terminate processing.</param>
/// <returns>Number of tokens consumed</returns>
public static int ConsumeUntil(this IUhtTokenReader tokenReader, string[] terminators)
{
int consumedTokens = 0;
while (!tokenReader.IsEOF)
{
ref UhtToken token = ref tokenReader.PeekToken();
foreach (string candidate in terminators)
{
if ((token.IsIdentifier() || token.IsSymbol()) && token.IsValue(candidate))
{
return consumedTokens;
}
}
tokenReader.ConsumeToken();
++consumedTokens;
}
return consumedTokens;
}
/// <summary>
/// Consume until the given terminator is found. Terminating token will be consumed
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="terminator">Terminating symbol</param>
/// <returns>Token reader</returns>
public static IUhtTokenReader ConsumeUntil(this IUhtTokenReader tokenReader, char terminator)
{
while (!tokenReader.IsEOF)
{
if (tokenReader.TryOptional(terminator))
{
break;
}
tokenReader.ConsumeToken();
}
return tokenReader;
}
/// <summary>
/// Throw an exception if the given symbol is found in the token stream
/// </summary>
/// <param name="tokenReader">Token reader</param>
/// <param name="candidate">Symbol or identifier to match</param>
/// <param name="message">Message thrown if found</param>
/// <returns>Token reader</returns>
/// <exception cref="UhtException"></exception>
public static IUhtTokenReader ExceptionIf(this IUhtTokenReader tokenReader, string candidate, string message)
{
UhtToken token = tokenReader.PeekToken();
if ((token.IsIdentifier() || token.IsSymbol()) && token.IsValue(candidate))
{
throw new UhtException(tokenReader, message);
}
return tokenReader;
}
}
}