308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			308 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | |||
|  | using System.Collections.Generic; | |||
|  | using EpicGames.Core; | |||
|  | using EpicGames.UHT.Types; | |||
|  | using UnrealSharpScriptGenerator.PropertyTranslators; | |||
|  | 
 | |||
|  | namespace UnrealSharpScriptGenerator.Utilities; | |||
|  | 
 | |||
|  | public enum ENameType | |||
|  | { | |||
|  |     Parameter, | |||
|  |     Property, | |||
|  |     Struct, | |||
|  |     Function | |||
|  | } | |||
|  | 
 | |||
|  | public static class NameMapper | |||
|  | { | |||
|  |     private static readonly List<string> ReservedKeywords = new() | |||
|  |     { | |||
|  |         "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", | |||
|  |         "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally",  | |||
|  |         "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", | |||
|  |         "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public",  | |||
|  |         "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch",  | |||
|  |         "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", | |||
|  |         "void", "volatile", "while", "System" | |||
|  |     }; | |||
|  | 
 | |||
|  |     public static string GetParameterName(this UhtProperty property) | |||
|  |     { | |||
|  |         string scriptName = ScriptifyName(property.GetScriptName(), ENameType.Parameter); | |||
|  | 
 | |||
|  |         if (property.Outer is not UhtFunction function) | |||
|  |         { | |||
|  |             return scriptName; | |||
|  |         } | |||
|  |          | |||
|  |         foreach (UhtProperty exportedProperty in function.Properties) | |||
|  |         { | |||
|  |             if (exportedProperty.HasAllFlags(EPropertyFlags.ReturnParm)) | |||
|  |             { | |||
|  |                 continue; | |||
|  |             } | |||
|  |              | |||
|  |             if (exportedProperty != property && scriptName == ScriptifyName(exportedProperty.GetScriptName(), ENameType.Parameter)) | |||
|  |             { | |||
|  |                 return PascalToCamelCase(exportedProperty.SourceName); | |||
|  |             } | |||
|  |         } | |||
|  |          | |||
|  |         return scriptName; | |||
|  |     } | |||
|  |      | |||
|  |     public static string GetPropertyName(this UhtProperty property) | |||
|  |     { | |||
|  |         string propertyName = ScriptifyName(property.GetScriptName(), ENameType.Property); | |||
|  |         if (property.Outer!.SourceName == propertyName || IsAKeyword(propertyName)) | |||
|  |         { | |||
|  |             propertyName = $"K2_{propertyName}"; | |||
|  |         } | |||
|  |         return TryResolveConflictingName(property, propertyName); | |||
|  |     } | |||
|  |      | |||
|  |     public static string GetStructName(this UhtType type) | |||
|  |     { | |||
|  |         if (type.EngineType is UhtEngineType.Interface or UhtEngineType.NativeInterface || type == Program.Factory.Session.UInterface) | |||
|  |         { | |||
|  |             return "I" + type.EngineName; | |||
|  |         } | |||
|  |          | |||
|  |         if (type is UhtClass uhtClass && uhtClass.IsChildOf(Program.BlueprintFunctionLibrary)) | |||
|  |         { | |||
|  |             return type.GetScriptName(); | |||
|  |         } | |||
|  | 
 | |||
|  |         return type.SourceName; | |||
|  |     } | |||
|  | 
 | |||
|  |     public static string ExportGetAssemblyName(this UhtType type) | |||
|  |     { | |||
|  |         string structName = type.GetStructName(); | |||
|  |         return $"typeof({structName}).GetAssemblyName()"; | |||
|  |     } | |||
|  |      | |||
|  |     public static string GetFullManagedName(this UhtType type) | |||
|  |     { | |||
|  |         return $"{type.GetNamespace()}.{type.GetStructName()}"; | |||
|  |     } | |||
|  |      | |||
|  |     static readonly string[] MetadataKeys = { "ScriptName", "ScriptMethod", "DisplayName" }; | |||
|  |     public static string GetScriptName(this UhtType type) | |||
|  |     { | |||
|  |         bool OnlyContainsLetters(string str) | |||
|  |         { | |||
|  |             foreach (char c in str) | |||
|  |             { | |||
|  |                 if (!char.IsLetter(c) && !char.IsWhiteSpace(c)) | |||
|  |                 { | |||
|  |                     return false; | |||
|  |                 } | |||
|  |             } | |||
|  |             return true; | |||
|  |         } | |||
|  |          | |||
|  |         foreach (var key in MetadataKeys) | |||
|  |         { | |||
|  |             string value = type.GetMetadata(key); | |||
|  |              | |||
|  |             if (string.IsNullOrEmpty(value) || !OnlyContainsLetters(value)) | |||
|  |             { | |||
|  |                 continue; | |||
|  |             } | |||
|  |              | |||
|  |             // Try remove whitespace from the value | |||
|  |             value = value.Replace(" ", ""); | |||
|  |             return value; | |||
|  |         } | |||
|  |          | |||
|  |         return type.SourceName; | |||
|  |     } | |||
|  |      | |||
|  |     public static string GetNamespace(this UhtType typeObj) | |||
|  |     { | |||
|  |         UhtType outer = typeObj; | |||
|  | 
 | |||
|  |         string packageShortName = ""; | |||
|  |         if (outer is UhtPackage package) | |||
|  |         { | |||
|  |             packageShortName = package.GetShortName(); | |||
|  |         } | |||
|  |         else | |||
|  |         { | |||
|  |             while (outer.Outer != null) | |||
|  |             { | |||
|  |                 outer = outer.Outer; | |||
|  |              | |||
|  |                 if (outer is UhtPackage header) | |||
|  |                 { | |||
|  |                     packageShortName = header.Package.GetShortName(); | |||
|  |                     break; | |||
|  |                 } | |||
|  |             } | |||
|  |         } | |||
|  |          | |||
|  |         if (string.IsNullOrEmpty(packageShortName)) | |||
|  |         { | |||
|  |             throw new Exception($"Failed to find package name for {typeObj}"); | |||
|  |         } | |||
|  |          | |||
|  |         return $"UnrealSharp.{packageShortName}"; | |||
|  |     } | |||
|  |      | |||
|  |     public static string GetFunctionName(this UhtFunction function) | |||
|  |     { | |||
|  |         string functionName = function.GetScriptName(); | |||
|  | 
 | |||
|  |         if (function.HasAnyFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate)) | |||
|  |         { | |||
|  |             functionName = DelegateBasePropertyTranslator.GetDelegateName(function); | |||
|  |         } | |||
|  |          | |||
|  |         if (functionName.StartsWith("K2_") || functionName.StartsWith("BP_")) | |||
|  |         { | |||
|  |             functionName = functionName.Substring(3); | |||
|  |         } | |||
|  | 
 | |||
|  |         if (function.IsInterfaceFunction() && functionName.EndsWith("_Implementation")) | |||
|  |         { | |||
|  |             functionName = functionName.Substring(0, functionName.Length - 15); | |||
|  |         } | |||
|  | 
 | |||
|  |         if (function.Outer is not UhtClass) | |||
|  |         { | |||
|  |             return functionName; | |||
|  |         } | |||
|  |          | |||
|  |         functionName = TryResolveConflictingName(function, functionName); | |||
|  | 
 | |||
|  |         return functionName; | |||
|  |     } | |||
|  |      | |||
|  |     public static string TryResolveConflictingName(UhtType type, string scriptName) | |||
|  |     { | |||
|  |         UhtType outer = type.Outer!; | |||
|  | 
 | |||
|  |         bool IsConflictingWithChild(List<UhtType> children) | |||
|  |         { | |||
|  |             foreach (UhtType child in children) | |||
|  |             { | |||
|  |                 if (child == type) | |||
|  |                 { | |||
|  |                     continue; | |||
|  |                 } | |||
|  |              | |||
|  |                 if (child is UhtProperty property) | |||
|  |                 { | |||
|  |                     if (scriptName == ScriptifyName(property.GetScriptName(), ENameType.Property)) | |||
|  |                     { | |||
|  |                         return true; | |||
|  |                     } | |||
|  |                 } | |||
|  |              | |||
|  |                 if (child is UhtFunction function) | |||
|  |                 { | |||
|  |                     if (scriptName == ScriptifyName(function.GetScriptName(), ENameType.Function)) | |||
|  |                     { | |||
|  |                         return true; | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             return false; | |||
|  |         } | |||
|  |          | |||
|  |         bool isConflicting = IsConflictingWithChild(outer.Children); | |||
|  |          | |||
|  |         if (!isConflicting && outer is UhtClass outerClass) | |||
|  |         { | |||
|  |             List<UhtClass> classInterfaces = outerClass.GetInterfaces(); | |||
|  |             foreach (UhtClass classInterface in classInterfaces) | |||
|  |             { | |||
|  |                 if (classInterface.AlternateObject is not UhtClass interfaceClass) | |||
|  |                 { | |||
|  |                     continue; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 UhtFunction? function = interfaceClass.FindFunctionByName(scriptName, (uhtFunction, s) => uhtFunction.GetFunctionName() == s); | |||
|  | 
 | |||
|  |                 if (function != null && type is UhtFunction typeAsFunction) | |||
|  |                 { | |||
|  |                     if (function.HasSameSignature(typeAsFunction)) | |||
|  |                     { | |||
|  |                         continue; | |||
|  |                     } | |||
|  |                      | |||
|  |                     isConflicting = true; | |||
|  |                     break; | |||
|  |                 } | |||
|  |             } | |||
|  |         } | |||
|  |          | |||
|  |         return isConflicting ? type.EngineName : scriptName; | |||
|  |     } | |||
|  | 
 | |||
|  |     public static string ScriptifyName(string engineName, ENameType nameType) | |||
|  |     { | |||
|  |         string strippedName = engineName; | |||
|  |         switch (nameType) | |||
|  |         { | |||
|  |             case ENameType.Parameter: | |||
|  |                 strippedName = StripPropertyPrefix(strippedName); | |||
|  |                 strippedName = PascalToCamelCase(strippedName); | |||
|  |                 break; | |||
|  |             case ENameType.Property: | |||
|  |                 strippedName = StripPropertyPrefix(strippedName); | |||
|  |                 break; | |||
|  |             case ENameType.Struct: | |||
|  |                 break; | |||
|  |             case ENameType.Function: | |||
|  |                 break; | |||
|  |             default: | |||
|  |                 throw new ArgumentOutOfRangeException(nameof(nameType), nameType, null); | |||
|  |         } | |||
|  |          | |||
|  |         return EscapeKeywords(strippedName); | |||
|  |     } | |||
|  |      | |||
|  |     public static string StripPropertyPrefix(string inName) | |||
|  |     { | |||
|  |         int nameOffset = 0; | |||
|  | 
 | |||
|  |         while (true) | |||
|  |         { | |||
|  |             // Strip the "b" prefix from bool names | |||
|  |             if (inName.Length - nameOffset >= 2 && inName[nameOffset] == 'b' && char.IsUpper(inName[nameOffset + 1])) | |||
|  |             { | |||
|  |                 nameOffset += 1; | |||
|  |                 continue; | |||
|  |             } | |||
|  | 
 | |||
|  |             // Strip the "In" prefix from names | |||
|  |             if (inName.Length - nameOffset >= 3 && inName[nameOffset] == 'I' && inName[nameOffset + 1] == 'n' && char.IsUpper(inName[nameOffset + 2])) | |||
|  |             { | |||
|  |                 nameOffset += 2; | |||
|  |                 continue; | |||
|  |             } | |||
|  |             break; | |||
|  |         } | |||
|  | 
 | |||
|  |         return nameOffset != 0 ? inName.Substring(nameOffset) : inName; | |||
|  |     } | |||
|  |      | |||
|  |     public static string EscapeKeywords(string name) | |||
|  |     { | |||
|  |         return IsAKeyword(name) || char.IsDigit(name[0]) ? $"_{name}" : name; | |||
|  |     } | |||
|  |      | |||
|  |     private static bool IsAKeyword(string name) | |||
|  |     { | |||
|  |         return ReservedKeywords.Contains(name); | |||
|  |     } | |||
|  |      | |||
|  |     private static string PascalToCamelCase(string name) | |||
|  |     { | |||
|  |         return char.ToLowerInvariant(name[0]) + name.Substring(1); | |||
|  |     } | |||
|  | } |