// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Reflection; using EpicGames.Core; using EpicGames.UHT.Parsers; using EpicGames.UHT.Tokenizer; using EpicGames.UHT.Utils; namespace EpicGames.UHT.Tables { /// /// Invoke the given method when the keyword is parsed. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class UhtKeywordAttribute : Attribute { /// /// Keyword table/scope being extended /// public string? Extends { get; set; } /// /// Name of the keyword /// public string? Keyword { get; set; } = null; /// /// Text to be displayed to the user when referencing this keyword /// public string? AllowText { get; set; } = null; /// /// If true, this applies to all scopes /// public bool AllScopes { get; set; } = false; /// /// If true, do not include in usage errors /// public bool DisableUsageError { get; set; } = false; /// /// List of the allowed compiler directives. /// public UhtCompilerDirective AllowedCompilerDirectives { get; set; } = UhtCompilerDirective.DefaultAllowedCheck; } /// /// Invoked as a last chance processor for a keyword /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class UhtKeywordCatchAllAttribute : Attribute { /// /// Table/scope to be extended /// public string? Extends { get; set; } } /// /// Delegate to notify a keyword was parsed /// /// Current scope being parsed /// The scope who's table was matched /// Matching token /// Results of the parsing public delegate UhtParseResult UhtKeywordDelegate(UhtParsingScope topScope, UhtParsingScope actionScope, ref UhtToken token); /// /// Delegate to invoke as a last chance processor for a keyword /// /// Current scope being parsed /// Matching token /// Results of the parsing public delegate UhtParseResult UhtKeywordCatchAllDelegate(UhtParsingScope topScope, ref UhtToken token); /// /// Defines a keyword /// public readonly struct UhtKeyword { /// /// Name of the keyword /// public string Name { get; } /// /// Delegate to invoke /// public UhtKeywordDelegate Delegate { get; } /// /// Text to be displayed to the user when referencing this keyword /// public string? AllowText { get; } /// /// If true, this applies to all scopes /// public bool AllScopes { get; } /// /// If true, do not include in usage errors /// public bool DisableUsageError { get; } /// /// List of the allowed compiler directives. /// public UhtCompilerDirective AllowedCompilerDirectives { get; } /// /// Construct a new keyword /// /// Name of the keyword /// Delegate to invoke /// Defining attribute public UhtKeyword(string name, UhtKeywordDelegate keywordDelegate, UhtKeywordAttribute? attribute) { Name = name; Delegate = keywordDelegate; if (attribute != null) { AllowText = attribute.AllowText; AllScopes = attribute.AllScopes; DisableUsageError = attribute.DisableUsageError; AllowedCompilerDirectives = attribute.AllowedCompilerDirectives; } else { AllowText = null; AllScopes = false; DisableUsageError = false; AllowedCompilerDirectives = UhtCompilerDirective.DefaultAllowedCheck; } } } /// /// Keyword table for a specific scope /// public class UhtKeywordTable : UhtLookupTable { /// /// List of catch-alls associated with this table /// public List CatchAlls { get; } = new List(); /// /// Construct a new keyword table /// public UhtKeywordTable() : base(StringViewComparer.Ordinal) { } /// /// Add the given value to the lookup table. It will throw an exception if it is a duplicate. /// /// Value to be added public UhtKeywordTable Add(UhtKeyword value) { base.Add(value.Name, value); return this; } /// /// Add the given catch-all to the table. /// /// The catch-all to be added public UhtKeywordTable AddCatchAll(UhtKeywordCatchAllDelegate catchAll) { CatchAlls.Add(catchAll); return this; } /// /// Merge the given keyword table. Duplicates in the BaseTypeTable will be ignored. /// /// Base table being merged public override void Merge(UhtLookupTableBase baseTable) { base.Merge(baseTable); CatchAlls.AddRange(((UhtKeywordTable)baseTable).CatchAlls); } } /// /// Table of all keyword tables /// public class UhtKeywordTables : UhtLookupTables { /// /// Construct the keyword tables /// public UhtKeywordTables() : base("keywords") { } /// /// Handle a keyword attribute /// /// Containing type /// Method information /// Defining attribute /// Thrown if the attribute isn't well defined public void OnKeywordCatchAllAttribute(Type type, MethodInfo methodInfo, UhtKeywordCatchAllAttribute keywordCatchAllAttribute) { if (String.IsNullOrEmpty(keywordCatchAllAttribute.Extends)) { throw new UhtIceException($"The 'KeywordCatchAlll' attribute on the {type.Name}.{methodInfo.Name} method doesn't have a table specified."); } UhtKeywordTable table = Get(keywordCatchAllAttribute.Extends); table.AddCatchAll((UhtKeywordCatchAllDelegate)Delegate.CreateDelegate(typeof(UhtKeywordCatchAllDelegate), methodInfo)); } /// /// Handle a keyword attribute /// /// Containing type /// Method information /// Defining attribute /// Thrown if the attribute isn't well defined public void OnKeywordAttribute(Type type, MethodInfo methodInfo, UhtKeywordAttribute keywordAttribute) { string name = UhtLookupTableBase.GetSuffixedName(type, methodInfo, keywordAttribute.Keyword, "Keyword"); if (String.IsNullOrEmpty(keywordAttribute.Extends)) { throw new UhtIceException($"The 'Keyword' attribute on the {type.Name}.{methodInfo.Name} method doesn't have a table specified."); } UhtKeywordTable table = Get(keywordAttribute.Extends); table.Add(new UhtKeyword(name, (UhtKeywordDelegate)Delegate.CreateDelegate(typeof(UhtKeywordDelegate), methodInfo), keywordAttribute)); } /// /// Log an unhandled error /// /// Destination message site /// Keyword public void LogUnhandledError(IUhtMessageSite messageSite, UhtToken token) { List? tables = null; foreach (KeyValuePair kvp in Tables) { UhtKeywordTable keywordTable = kvp.Value; if (keywordTable.Internal) { continue; } if (keywordTable.TryGetValue(token.Value, out UhtKeyword info)) { if (info.DisableUsageError) { // Do not log anything for this keyword in this table } else { tables ??= new List(); if (info.AllowText != null) { tables.Add($"{keywordTable.UserName} {info.AllowText}"); } else { tables.Add(keywordTable.UserName); } } } } if (tables != null) { string text = UhtUtilities.MergeTypeNames(tables, "and"); messageSite.LogError(token.InputLine, $"Invalid use of keyword '{token.Value}'. It may only appear in {text} scopes"); } } } }