// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using EpicGames.UHT.Tokenizer;
namespace EpicGames.UHT.Utils
{
///
/// Type of message
///
public enum UhtMessageType
{
///
/// The message is an error and goes to the log and console
///
Error,
///
/// The message is a warning and goes to the log and console
///
Warning,
///
/// The message is for information only and goes to the log and console
///
Info,
///
/// The message is for debugging and goes to the log only
///
Trace,
///
/// The message is an informational message about deprecated patterns
///
Deprecation,
///
/// The message is an internal error and goes to the log and console
///
Ice,
}
///
/// A message session is the destination object for all generated messages
///
public interface IUhtMessageSession
{
///
/// Add the given message
///
/// The message to be added
void AddMessage(UhtMessage message);
}
///
/// A message source represents the source file where the message occurred.
///
public interface IUhtMessageSource
{
///
/// File path of the file being parsed
///
string MessageFilePath { get; }
///
/// The full file path of being parsed
///
string MessageFullFilePath { get; }
///
/// If true, the source is a source fragment from the testing harness
///
bool MessageIsFragment { get; }
///
/// If this is a fragment, this is the container file path of the fragment
///
string MessageFragmentFilePath { get; }
///
/// If this is a fragment, this is the container full file path of the fragment
///
string MessageFragmentFullFilePath { get; }
///
/// If this is a fragment, this is the line number in the container file where the fragment is defined.
///
int MessageFragmentLineNumber { get; }
}
///
/// A message site can automatically provide a line number where the site was defined
/// in the source. If no line number is provided when the message is created or if the
/// site doesn't support this interface, the line number of '1' will be used.
///
public interface IUhtMessageLineNumber
{
///
/// Line number where the type was defined.
///
int MessageLineNumber { get; }
}
///
/// This interface provides a mechanism for things to provide more context for an error
///
public interface IUhtMessageExtraContext
{
///
/// Enumeration of objects to add as extra context.
///
IEnumerable? MessageExtraContext { get; }
}
///
/// A message site is any object that can generate a message. In general, all
/// types are also message sites. This provides a convenient method to log messages
/// where the type was defined.
///
public interface IUhtMessageSite
{
///
/// Destination message session for the messages
///
public IUhtMessageSession MessageSession { get; }
///
/// Source file generating messages
///
public IUhtMessageSource? MessageSource { get; }
///
/// Optional line number where type was defined
///
public IUhtMessageLineNumber? MessageLineNumber { get; }
}
///
/// Represents a UHT message
///
public struct UhtMessage
{
///
/// The type of message
///
public UhtMessageType MessageType { get; set; }
///
/// Optional message source for the message. Either the MessageSource or FilePath must be set.
///
public IUhtMessageSource? MessageSource { get; set; }
///
/// Optional file path for the message. Either the MessageSource or FilePath must be set.
///
public string? FilePath { get; set; }
///
/// Line number where error occurred.
///
public int LineNumber { get; set; }
///
/// Text of the message
///
public string Message { get; set; }
///
/// Make a new message with the given settings
///
/// Type of message
/// Source of the message
/// File path of the message
/// Line number where message occurred
/// Text of the message
/// Created message
public static UhtMessage MakeMessage(UhtMessageType messageType, IUhtMessageSource? messageSource, string? filePath, int lineNumber, string message)
{
return new UhtMessage
{
MessageType = messageType,
MessageSource = messageSource,
FilePath = filePath,
LineNumber = lineNumber,
Message = message
};
}
///
/// Format an object to be included in a message
///
/// Contextual object
/// Formatted context
public static string FormatContext(object context)
{
if (context is IUhtMessageExtraContext extraContextInterface)
{
StringBuilder builder = new();
Append(builder, extraContextInterface, false);
return builder.ToString();
}
else if (context is UhtToken token)
{
switch (token.TokenType)
{
case UhtTokenType.EndOfFile:
return "EOF";
case UhtTokenType.EndOfDefault:
return "'end of default value'";
case UhtTokenType.EndOfType:
return "'end of type'";
case UhtTokenType.EndOfDeclaration:
return "'end of declaration'";
case UhtTokenType.StringConst:
return "string constant";
default:
return $"'{token.Value}'";
}
}
else if (context is char c)
{
return $"'{c}'";
}
else if (context is string[] stringArray)
{
return UhtUtilities.MergeTypeNames(stringArray, "or", true);
}
else
{
return context.ToString() ?? String.Empty;
}
}
///
/// Append message context
///
/// Destination builder
/// Extra context to append
/// If true, always include the separator
public static void Append(StringBuilder builder, IUhtMessageExtraContext? messageExtraContextInterface, bool alwaysIncludeSeparator)
{
if (messageExtraContextInterface == null)
{
return;
}
IEnumerable? extraContextList = messageExtraContextInterface.MessageExtraContext;
if (extraContextList != null)
{
int startingLength = builder.Length;
foreach (object? ec in extraContextList)
{
if (ec != null)
{
if (builder.Length != startingLength || alwaysIncludeSeparator)
{
builder.Append(" in ");
}
builder.Append(UhtMessage.FormatContext(ec));
}
}
}
}
}
///
/// A placeholder message site
///
public class UhtEmptyMessageSite : IUhtMessageSite
{
///
public IUhtMessageSession MessageSession => throw new NotImplementedException();
///
public IUhtMessageSource? MessageSource => throw new NotImplementedException();
///
public IUhtMessageLineNumber? MessageLineNumber => throw new NotImplementedException();
}
///
/// Creates a message site from a message session interface and a message source interface
///
public class UhtSimpleMessageSite : IUhtMessageSite
{
private readonly IUhtMessageSession _messageSession;
private IUhtMessageSource? _messageSource;
#region IUHTMessageSite implementation
///
public IUhtMessageSession MessageSession => _messageSession;
///
public IUhtMessageSource? MessageSource { get => _messageSource; set => _messageSource = value; }
///
public IUhtMessageLineNumber? MessageLineNumber => null;
#endregion
///
/// Create a simple message site for the given session and source
///
/// Associated message session
/// Source for the messages
public UhtSimpleMessageSite(IUhtMessageSession messageSession, IUhtMessageSource? messageSource = null)
{
_messageSession = messageSession;
_messageSource = messageSource;
}
}
///
/// Simple message site for the given file.
///
public class UhtSimpleFileMessageSite : UhtSimpleMessageSite, IUhtMessageSource
{
#region IUHTMessageSource implementation
///
public string MessageFilePath => _filePath;
///
public string MessageFullFilePath => _filePath;
///
public bool MessageIsFragment => false;
///
public string MessageFragmentFilePath => "";
///
public string MessageFragmentFullFilePath => "";
///
public int MessageFragmentLineNumber => -1;
#endregion
private readonly string _filePath;
///
/// Create a simple file site
///
/// Associated message session
/// File associated with the site
public UhtSimpleFileMessageSite(IUhtMessageSession messageSession, string filePath) : base(messageSession, null)
{
_filePath = filePath;
MessageSource = this;
}
}
///
/// Series of extensions for message sites.
///
public static class UhtMessageSiteExtensions
{
///
/// Get the line number generating the error
///
/// The message site generating the error.
/// An override line number
/// Either the overriding line number or the line number from the message site.
public static int GetLineNumber(this IUhtMessageSite messageSite, int lineNumber = -1)
{
if (lineNumber != -1)
{
return lineNumber;
}
else if (messageSite.MessageLineNumber != null)
{
return messageSite.MessageLineNumber.MessageLineNumber;
}
else
{
return 1;
}
}
///
/// Log an error
///
/// Message site associated with the message
/// Line number of the error
/// Text of the error
/// Addition context to be appended to the error message
public static void LogError(this IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Error, messageSite, lineNumber, message, extraContext);
}
///
/// Log an error
///
/// Message site associated with the message
/// Text of the error
/// Addition context to be appended to the error message
public static void LogError(this IUhtMessageSite messageSite, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Error, messageSite, -1, message, extraContext);
}
///
/// Log a warning
///
/// Message site associated with the message
/// Line number of the warning
/// Text of the warning
/// Addition context to be appended to the error message
public static void LogWarning(this IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Warning, messageSite, lineNumber, message, extraContext);
}
///
/// Log a warning
///
/// Message site associated with the message
/// Text of the warning
/// Addition context to be appended to the error message
public static void LogWarning(this IUhtMessageSite messageSite, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Warning, messageSite, -1, message, extraContext);
}
///
/// Log a message directly to the log
///
/// Message site associated with the message
/// Line number of the information
/// Text of the information
/// Addition context to be appended to the error message
public static void LogTrace(this IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Trace, messageSite, lineNumber, message, extraContext);
}
///
/// Log a message directly to the log
///
/// Message site associated with the message
/// Text of the information
/// Addition context to be appended to the error message
public static void LogTrace(this IUhtMessageSite messageSite, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Trace, messageSite, -1, message, extraContext);
}
///
/// Log information
///
/// Message site associated with the message
/// Line number of the information
/// Text of the information
/// Addition context to be appended to the error message
public static void LogInfo(this IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Info, messageSite, lineNumber, message, extraContext);
}
///
/// Log a information
///
/// Message site associated with the message
/// Text of the information
/// Addition context to be appended to the error message
public static void LogInfo(this IUhtMessageSite messageSite, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Info, messageSite, -1, message, extraContext);
}
///
/// Log deprecation
///
/// Message site associated with the message
/// Line number of the information
/// Text of the information
/// Addition context to be appended to the error message
public static void LogDeprecation(this IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Deprecation, messageSite, lineNumber, message, extraContext);
}
///
/// Log a deprecation
///
/// Message site associated with the message
/// Text of the information
/// Addition context to be appended to the error message
public static void LogDeprecation(this IUhtMessageSite messageSite, string message, object? extraContext = null)
{
LogMessage(UhtMessageType.Deprecation, messageSite, -1, message, extraContext);
}
///
/// Log a message
///
/// Type of the message being generated
/// Message site associated with the message
/// Line number of the information
/// Text of the information
/// Addition context to be appended to the error message
private static void LogMessage(UhtMessageType messageType, IUhtMessageSite messageSite, int lineNumber, string message, object? extraContext)
{
if (extraContext != null)
{
message = $"{message} in {UhtMessage.FormatContext(extraContext)}";
}
messageSite.MessageSession.AddMessage(UhtMessage.MakeMessage(messageType, messageSite.MessageSource, null, messageSite.GetLineNumber(lineNumber), message));
}
}
///
/// Thread based message context. Used to improve performance by avoiding allocations.
///
public sealed class UhtTlsMessageExtraContext : IUhtMessageExtraContext
{
private Stack? _extraContexts;
private static readonly ThreadLocal s_tls = new(() => new UhtTlsMessageExtraContext());
#region IUHTMessageExtraContext implementation
IEnumerable? IUhtMessageExtraContext.MessageExtraContext => _extraContexts;
#endregion
///
/// Add an extra context
///
///
public void PushExtraContext(object? exceptionContext)
{
_extraContexts ??= new Stack(8);
_extraContexts.Push(exceptionContext);
}
///
/// Pop the top most extra context
///
public void PopExtraContext()
{
_extraContexts?.Pop();
}
///
/// Get the extra context associated with this thread
///
/// Extra context
public static UhtTlsMessageExtraContext? GetTls() { return UhtTlsMessageExtraContext.s_tls.Value; }
///
/// Get the extra context interface
///
/// Extra context interface
public static IUhtMessageExtraContext? GetMessageExtraContext() { return UhtTlsMessageExtraContext.s_tls.Value; }
}
///
/// A "using" object to automate the push/pop of extra context to the thread's current extra context
///
public readonly struct UhtMessageContext : IDisposable
{
private readonly UhtTlsMessageExtraContext? _stack;
///
/// Construct a new entry
///
/// Extra context to be added
public UhtMessageContext(object? extraContext)
{
_stack = UhtTlsMessageExtraContext.GetTls();
_stack?.PushExtraContext(extraContext);
}
///
/// Replace the extra context. This replaces the top level context and thus
/// can have unexpected results if done when a more deeper context has been added
///
/// New extra context
public void Reset(object? extraContext)
{
if (_stack != null)
{
_stack.PopExtraContext();
_stack.PushExtraContext(extraContext);
}
}
///
/// Dispose the object and auto-remove the added context
///
public void Dispose()
{
_stack?.PopExtraContext();
}
}
}