// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using EpicGames.Core; using EpicGames.UHT.Types; namespace EpicGames.UHT.Utils { /// /// Symbol table /// internal class UhtSymbolTable { /// /// Represents symbol name lookup chain start. Symbol chains are based on /// the caseless name. /// struct Lookup { /// /// The type index of the symbol /// public int SymbolIndex { get; set; } /// /// When searching the caseless chain, the cased index is used to match the symbol based /// on the case named. /// public int CasedIndex { get; set; } } /// /// Entry in the symbol table /// struct Symbol { /// /// The type associated with the symbol /// public UhtType Type { get; set; } /// /// Mask of different find options which will match this symbol /// public UhtFindOptions MatchOptions { get; set; } /// /// The case lookup index for matching by case /// public int CasedIndex { get; set; } /// /// The next index in the symbol change based on cassless lookup /// public int NextIndex { get; set; } /// /// The last index in the chain. This index is only used when the symbol entry is also acting as the list /// public int LastIndex { get; set; } } /// /// Number of unique cased symbol names. /// private int _casedCount = 0; /// /// Case name lookup table that returns the symbol index and the case index /// private readonly Dictionary _casedDictionary = new(StringComparer.Ordinal); /// /// Caseless name lookup table that returns the symbol index /// private readonly Dictionary _caselessDictionary = new(StringComparer.OrdinalIgnoreCase); /// /// Collection of symbols in the table /// private readonly Symbol[] _symbols; /// /// Constructs a new symbol table. /// /// Number of types in the table. public UhtSymbolTable(int typeCount) { _symbols = new Symbol[typeCount]; } /// /// Add a new type to the symbol table /// /// The type being added /// The name of the type which could be the source name or the engine name public void Add(UhtType type, string name) { if (_casedDictionary.TryGetValue(name, out Lookup existing)) { AddExisting(type, existing.CasedIndex, existing.SymbolIndex); return; } int casedIndex = ++_casedCount; if (_caselessDictionary.TryGetValue(name, out int symbolIndex)) { _casedDictionary.Add(name, new Lookup { SymbolIndex = symbolIndex, CasedIndex = casedIndex }); AddExisting(type, casedIndex, symbolIndex); return; } symbolIndex = type.TypeIndex; _symbols[symbolIndex] = new Symbol { Type = type, MatchOptions = type.EngineType.FindOptions(), CasedIndex = casedIndex, NextIndex = 0, LastIndex = symbolIndex }; _caselessDictionary.Add(name, symbolIndex); _casedDictionary.Add(name, new Lookup { SymbolIndex = symbolIndex, CasedIndex = casedIndex }); } /// /// Replace an entry in the symbol table. This is used during property resolution to replace the /// parser property (which could not resolve the property prior to the symbol table being created) /// with the fully resolved property. /// /// The old type being replaced. /// The new type. /// The name of the type. /// Thrown if the symbol wasn't found. public void Replace(UhtType oldType, UhtType newType, string name) { if (_caselessDictionary.TryGetValue(name, out int symbolIndex)) { for (; symbolIndex != 0; symbolIndex = _symbols[symbolIndex].NextIndex) { if (_symbols[symbolIndex].Type == oldType) { _symbols[symbolIndex].Type = newType; return; } } } throw new UhtIceException("Attempt to replace a type that wasn't found"); } /// /// Hide the given type in the symbol table /// /// Type to be hidden /// The name of the type. /// Thrown if the symbol wasn't found. public void Hide(UhtType typeToHide, string name) { if (_caselessDictionary.TryGetValue(name, out int symbolIndex)) { for (; symbolIndex != 0; symbolIndex = _symbols[symbolIndex].NextIndex) { if (_symbols[symbolIndex].Type == typeToHide) { _symbols[symbolIndex].MatchOptions = 0; return; } } } throw new UhtIceException("Attempt to hide a type that wasn't found"); } /// /// Lookup the given name using cased string compare. /// /// Starting type used to limit the scope of the search. /// Options controlling what is search and what is returned. /// Name to locate. /// Found type or null if not found. public UhtType? FindCasedType(UhtType? startingType, UhtFindOptions options, string name) { if (_casedDictionary.TryGetValue(name, out Lookup existing)) { return FindType(startingType, options, existing); } return null; } /// /// Lookup the given name using caseless string compare. /// /// Starting type used to limit the scope of the search. /// Options controlling what is search and what is returned. /// Name to locate. /// Found type or null if not found. public UhtType? FindCaselessType(UhtType? startingType, UhtFindOptions options, string name) { if (_caselessDictionary.TryGetValue(name, out int symbolIndex)) { return FindType(startingType, options, new Lookup { SymbolIndex = symbolIndex, CasedIndex = 0 }); } return null; } /// /// Lookup the given name. /// /// Starting type used to limit the scope of the search. /// Options controlling what is search and what is returned. /// Starting lookup location. /// Found type or null if not found. private UhtType? FindType(UhtType? startingType, UhtFindOptions options, Lookup lookup) { if (startingType != null) { UhtType? found = FindTypeSuperChain(startingType, options, ref lookup); if (found != null) { return found; } found = FindTypeOuterChain(startingType, options, ref lookup); if (found != null) { return found; } if (!options.HasAnyFlags(UhtFindOptions.NoIncludes)) { UhtHeaderFile headerFile = startingType.HeaderFile; foreach (UhtHeaderFile includedFile in headerFile.IncludedHeaders) { found = FindSymbolChain(includedFile, options, ref lookup); if (found != null) { break; } } if (found != null) { return found; } } } // Global search. Match anything that has an owner of parent if (!options.HasAnyFlags(UhtFindOptions.NoGlobal)) { for (int index = lookup.SymbolIndex; index != 0; index = _symbols[index].NextIndex) { if (IsMatch(options, index, lookup.CasedIndex) && _symbols[index].Type.Outer is UhtPackage) { return _symbols[index].Type; } } } // Can't find at all return null; } /// /// Lookup the given name using the super class/struct chain. /// /// Starting type used to limit the scope of the search. /// Options controlling what is search and what is returned. /// Starting lookup location. /// Found type or null if not found. private UhtType? FindTypeSuperChain(UhtType startingType, UhtFindOptions options, ref Lookup lookup) { UhtType? currentType = startingType; // In a super chain search, we have to start at a UHTStruct that contributes to the symbol table for (; currentType != null; currentType = currentType.Outer) { if (currentType is UhtStruct structObj && structObj.EngineType.AddChildrenToSymbolTable()) { break; } } // Not symbol that supports a super chain if (currentType == null) { return null; } // If requested, skip self if (options.HasAnyFlags(UhtFindOptions.NoSelf) && currentType == startingType) { if (currentType is UhtFunction) { if (currentType.Outer is UhtStruct outerStructType) { currentType = outerStructType; } else { currentType = null; } } else { currentType = ((UhtStruct)currentType).Super; } } // Search the chain for (; currentType != null; currentType = ((UhtStruct)currentType).Super) { UhtType? foundType = FindSymbolChain(currentType, options, ref lookup); if (foundType != null) { return foundType; } if (options.HasAnyFlags(UhtFindOptions.NoParents)) { return null; } } return null; } /// /// Lookup the given name using the outer chain /// /// Starting type used to limit the scope of the search. /// Options controlling what is search and what is returned. /// Starting lookup location. /// Found type or null if not found. private UhtType? FindTypeOuterChain(UhtType startingType, UhtFindOptions options, ref Lookup lookup) { UhtType? currentType = startingType; // If requested, skip self if (options.HasAnyFlags(UhtFindOptions.NoSelf) && currentType == startingType) { currentType = currentType.Outer; } // Search the chain for (; currentType != null; currentType = currentType.Outer) { // When we hit the package, we assume that we want to do a search of the whole header file. But this // is only done when starting type isn't the package itself if (currentType is UhtPackage) { return startingType != currentType ? FindSymbolChain(startingType.HeaderFile, options, ref lookup) : null; } UhtType? foundType = FindSymbolChain(currentType, options, ref lookup); if (foundType != null) { return foundType; } if (options.HasAnyFlags(UhtFindOptions.NoOuter)) { return null; } } return null; } /// /// Lookup the given name /// /// Matching owner. /// Options controlling what is search and what is returned. /// Starting lookup location. /// Found type or null if not found. private UhtType? FindSymbolChain(UhtType owner, UhtFindOptions options, ref Lookup lookup) { for (int index = lookup.SymbolIndex; index != 0; index = _symbols[index].NextIndex) { if (IsMatch(options, index, lookup.CasedIndex) && _symbols[index].Type.Outer == owner) { return _symbols[index].Type; } } return null; } /// /// Lookup the given name /// /// Header file being searched. /// Options controlling what is search and what is returned. /// Starting lookup location. /// Found type or null if not found. private UhtType? FindSymbolChain(UhtHeaderFile header, UhtFindOptions options, ref Lookup lookup) { for (int index = lookup.SymbolIndex; index != 0; index = _symbols[index].NextIndex) { // Match on the same header file, but only top level types if (IsMatch(options, index, lookup.CasedIndex) && _symbols[index].Type.HeaderFile == header && _symbols[index].Type.Outer is UhtPackage) { return _symbols[index].Type; } } return null; } /// /// Add a new type to the given symbol chain /// /// Type being added /// Cased index /// Symbol index private void AddExisting(UhtType type, int casedIndex, int symbolIndex) { int typeIndex = type.TypeIndex; _symbols[typeIndex] = new Symbol { Type = type, MatchOptions = type.EngineType.FindOptions(), CasedIndex = casedIndex, NextIndex = 0, LastIndex = 0 }; _symbols[_symbols[symbolIndex].LastIndex].NextIndex = typeIndex; _symbols[symbolIndex].LastIndex = typeIndex; } /// /// Test to see if the given symbol matches the options /// /// Options to match /// Symbol index /// Case index /// True if the symbol is a match private bool IsMatch(UhtFindOptions options, int symbolIndex, int casedIndex) { return (casedIndex == 0 || casedIndex == _symbols[symbolIndex].CasedIndex) && (_symbols[symbolIndex].MatchOptions & options) != 0; } } }