// 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; } } }