// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Tokenizer
{
///
/// Collection of token reader extensions to help with flow control
///
public static class UhtTokenReaderFlowControlExtensions
{
///
/// Parse an optional list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Action to be invoked for each list element.
/// True if a list was read
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;
}
///
/// Parse a required list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Action to be invoked for each list element.
/// Token reader
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);
}
///
/// Parse a required list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Extra context for error messages
/// Action to be invoked for each list element.
/// Token reader
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);
}
///
/// Parse a required list. Initiating token must have already been parsed
///
/// Token reader
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Action to be invoked for each list element.
/// Token reader
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, Action action)
{
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, null, action);
}
///
/// Parse a required list. Initiating token must have already been parsed
///
/// Token reader
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Extra context for error messages
/// Action to be invoked for each list element.
/// Token reader
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;
}
}
///
/// Parse an optional list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Delegate to invoke with the parsed token list
/// True if a list was read
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;
}
///
/// Parse a required list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Delegate to invoke with the parsed token list
/// Token reader
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);
}
///
/// Parse a required list
///
/// Token reader
/// Initiating symbol
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Extra context for error messages
/// Delegate to invoke with the parsed token list
/// Token reader
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);
}
///
/// Parse a required list. Initiating token must have already been parsed
///
/// Token reader
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Delegate to invoke with the parsed token list
/// Token reader
public static IUhtTokenReader RequireList(this IUhtTokenReader tokenReader, char terminator, char separator, bool allowTrailingSeparator, UhtTokensDelegate tokensDelegate)
{
return tokenReader.RequireList(terminator, separator, allowTrailingSeparator, null, tokensDelegate);
}
///
/// Parse a required list. Initiating token must have already been parsed
///
/// Token reader
/// Terminating symbol
/// Separator symbol
/// If true, allow trailing separators
/// Extra context for error messages
/// Delegate to invoke with the parsed token list
/// Token reader
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 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());
}
}
}
///
/// Consume a block of tokens bounded by the two given symbols.
///
/// Token reader
/// The next token must be the given symbol.
/// The tokens are read until the given symbol is found. The terminating symbol will be consumed.
/// Extra context for any error messages
/// The input token reader
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;
}
///
/// Invoke action while the next token is the given string
///
/// Token reader
/// Text to match to continue invoking Action
/// Action to invoke if and only if the prior text was parsed.
/// Token reader
public static IUhtTokenReader While(this IUhtTokenReader tokenReader, string text, Action action)
{
while (tokenReader.TryOptional(text))
{
action();
}
return tokenReader;
}
///
/// Invoke action while the next token is the given string
///
/// Token reader
/// Text to match to continue invoking Action
/// Action to invoke if and only if the prior text was parsed.
/// Token reader
public static IUhtTokenReader While(this IUhtTokenReader tokenReader, char text, Action action)
{
while (tokenReader.TryOptional(text))
{
action();
}
return tokenReader;
}
///
/// Consume tokens until one of the strings are found. Terminating token will not be consumed.
///
/// Token reader
/// Strings that will terminate processing.
/// Number of tokens consumed
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;
}
///
/// Consume until the given terminator is found. Terminating token will be consumed
///
/// Token reader
/// Terminating symbol
/// Token reader
public static IUhtTokenReader ConsumeUntil(this IUhtTokenReader tokenReader, char terminator)
{
while (!tokenReader.IsEOF)
{
if (tokenReader.TryOptional(terminator))
{
break;
}
tokenReader.ConsumeToken();
}
return tokenReader;
}
///
/// Throw an exception if the given symbol is found in the token stream
///
/// Token reader
/// Symbol or identifier to match
/// Message thrown if found
/// Token reader
///
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;
}
}
}