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